/* -*- tab-width: 4; -*- */
/* vi: set sw=2 ts=4 expandtab: */

/**
 * @internal
 * @file
 * @~English
 *
 * @brief Test ktxTexture API functions.
 *
 * @author Mark Callow, github.com/MarkCallow
 */

/*
 * Copyright 2010-2020 Mark Callow, <khronos at callow dot im>.
 * SPDX-License-Identifier: Apache-2.0
 */

#if defined(_WIN32)
  #define _CRT_SECURE_NO_WARNINGS
  #if _MSC_VER < 1900
    #define snprintf _snprintf
  #endif
#endif

#include <filesystem>
#include <fstream>
#include <iostream>
#include <string>
//#include <sys/types.h>
#include <sys/stat.h>
#include <limits.h>
#include <stdint.h>
#include <string.h>
#include "GL/glcorearb.h"
#include "ktx.h"
#include "ktxint.h"
#include "texture.h"
#include "texture1.h"
#include "texture2.h"
#include "gtest/gtest.h"
#include "wthelper.h"
#include "vk_format.h"

#define ROUNDING(x) \
        (3 - ((x + KTX_GL_UNPACK_ALIGNMENT-1) % KTX_GL_UNPACK_ALIGNMENT));

#if defined(TestNoMetadata)
extern ktx_bool_t __disableWriterMetadata__;
#endif

namespace {

namespace fs = std::filesystem;

// Recursive function to return the greatest common divisor of a and b.
static uint32_t
gcd(uint32_t a, uint32_t b) {
    if (a == 0)
        return b;
    return gcd(b % a, a);
}

// Function to return the least common multiple of a & 4.
uint32_t
lcm4(uint32_t a)
{
    if (!(a & 0x03))
        return a;  // a is a multiple of 4.
    return (a*4) / gcd(a, 4);
}

//-------------------------------------------------------
// Helper for base fixture & ktxTexture_WriterTest cases.
//-------------------------------------------------------

template<typename component_type, ktx_uint32_t numComponents,
         GLenum internalformat>
class TextureWriterTestHelper
    : public WriterTestHelper<component_type, numComponents, internalformat> {
  public:
    using typename WriterTestHelper<component_type, numComponents, internalformat>::createFlags;
    using typename WriterTestHelper<component_type, numComponents, internalformat>::createFlagBits;
    using WriterTestHelper<component_type, numComponents, internalformat>::images;
    using WriterTestHelper<component_type, numComponents, internalformat>::imageList;
    using WriterTestHelper<component_type, numComponents, internalformat>::width;
    using WriterTestHelper<component_type, numComponents, internalformat>::height;
    using WriterTestHelper<component_type, numComponents, internalformat>::levelsFromSize;

    TextureWriterTestHelper() {}

    void
    resize(createFlags flags, ktx_uint32_t layers, ktx_uint32_t faces,
           ktx_uint32_t dimensions,
           ktx_uint32_t w, ktx_uint32_t h, ktx_uint32_t d)
    {
        WriterTestHelper<component_type, numComponents, internalformat>::resize(
                                               flags, layers, faces,
                                               dimensions,
                                               w, h, d);
        createInfo.resize(flags, layers, faces,
                          dimensions, w, h, d);
    }

    // Compare images as loaded into a ktxTexture1 object with our image.
    bool
    compareTexture1Images(ktx_uint8_t* pData)
    {
        for (ktx_uint32_t level = 0; level < images.size(); level++) {
            ktx_uint32_t levelWidth = MAX(1, width >> level);
            ktx_uint32_t levelHeight = MAX(1, height >> level);
            ktx_size_t rowBytes = levelWidth * sizeof(component_type) * numComponents;
            ktx_uint32_t rowPadding = ROUNDING(rowBytes);
            ktx_size_t paddedImageBytes = (rowBytes + rowPadding) * levelHeight;
            for (ktx_uint32_t layer = 0; layer < images[0].size(); layer++) {
                for (ktx_uint32_t faceSlice = 0; faceSlice < images[level][layer].size(); faceSlice++) {
                    if (rowPadding == 0) {
                        if (memcmp(images[level][layer][faceSlice].data(), pData,
                                   images[level][layer][faceSlice].size() * sizeof(component_type)))
                            return false;
                        pData += paddedImageBytes;
                    } else {
                        ktx_uint8_t* pImage = (ktx_uint8_t*)images[level][layer][faceSlice].data();
                        for (ktx_uint32_t row = 0; row < levelHeight; row++) {
                            if (memcmp(pImage, pData, rowBytes))
                                return false;
                            pImage += rowBytes;
                            pData += rowBytes + rowPadding;
                        }
                    }
                }
            }
        }
        return true;
    }

    // Compare images as loaded into a ktxTexture2 object with our image.
    bool
    compareTexture2Images(ktx_uint8_t* pData)
    {
        for (ktx_int32_t level = (ktx_int32_t)images.size() - 1; level >= 0; level--) {
            ktx_uint32_t levelWidth = MAX(1, width >> level);
            ktx_uint32_t levelHeight = MAX(1, height >> level);
            ktx_uint32_t texelBlockSize = sizeof(component_type) * numComponents;
            ktx_uint32_t requiredLevelAlignment = lcm4(texelBlockSize);
            ktx_size_t rowBytes = levelWidth * texelBlockSize;
            ktx_size_t imageBytes = rowBytes * levelHeight;
            for (ktx_uint32_t layer = 0; layer < images[0].size(); layer++) {
                for (ktx_uint32_t faceSlice = 0; faceSlice < images[level][layer].size(); faceSlice++) {
                    if (memcmp(images[level][layer][faceSlice].data(), pData,
                               images[level][layer][faceSlice].size() * sizeof(component_type)))
                        return false;
                    pData += imageBytes;
                }
            }
            pData += _KTX_PADN_LEN(requiredLevelAlignment, imageBytes);
        }
        return true;
    }

    KTX_error_code
    copyImagesToTexture(ktxTexture1* texture) {
        KTX_error_code result = KTX_SUCCESS;

        for (ktx_uint32_t level = 0; level < images.size(); level++) {
            for (ktx_uint32_t layer = 0; layer < images[level].size(); layer++) {
                for (ktx_uint32_t faceSlice = 0; faceSlice < images[level][layer].size(); faceSlice++) {
                    ktx_size_t imageBytes = images[level][layer][faceSlice].size() * sizeof(component_type);
                    ktx_uint8_t* imageDataPtr = (ktx_uint8_t*)(images[level][layer][faceSlice].data());
                    result = ktxTexture1_SetImageFromMemory(texture,
                                                            level, layer,
                                                            faceSlice,
                                                            imageDataPtr,
                                                            imageBytes);
                   if (result != KTX_SUCCESS)
                       break;
                }
            }
        }
        return result;
    }

    class createInfo : public ktxTextureCreateInfo {
      public:
        createInfo() {
            glInternalformat = internalformat;
        }

        void resize(createFlags flags,
                    ktx_uint32_t layers, ktx_uint32_t faces,
                    ktx_uint32_t dimensions, ktx_uint32_t w,
                    ktx_uint32_t h, ktx_uint32_t d)
        {
            baseWidth = w;
            baseHeight = h;
            baseDepth = d;
            this->numDimensions = dimensions;
            generateMipmaps = flags & createFlagBits::eGenerateMipmaps
                              ? KTX_TRUE : KTX_FALSE;
            isArray = flags & createFlagBits::eArray ? KTX_TRUE : KTX_FALSE;
            this->numFaces = faces;
            this->numLayers = layers;
            numLevels = flags & createFlagBits::eMipmapped
                        ? levelsFromSize(w, h, d) : 1;
        };
    } createInfo;

};

const ktx_uint8_t ktxId[12] = KTX_IDENTIFIER_REF;
const ktx_uint8_t ktxId2[12] = KTX2_IDENTIFIER_REF;

///////////////////////////////////////////////////////////
// Test fixtures
///////////////////////////////////////////////////////////

//----------------------------------------------------
// Base fixture for ktxTexture and related test cases.
//----------------------------------------------------

typedef TextureWriterTestHelper<GLubyte, 4, GL_RGBA8>::createFlagBits createFlagBits;

template<typename component_type, ktx_uint32_t numComponents,
         GLenum internalformat>
class ktxTextureTestBase : public ::testing::Test {
  protected:
    ktxTextureTestBase(ktxFormatVersionEnum fv) : pixelSize(16)
    {
        helper.resize(createFlagBits::eMipmapped, 1, 1, 2, 16, 16, 1);
        // Create a KTX file in memory for testing.

        KTX_error_code errorCode;

        ktxMemFile = 0;
        iterCbCalls = 0;

        mipLevels = helper.numLevels;

        // Create the in-memory KTX file

        ktxTexture* texture = 0;
        if (fv == KTX_FORMAT_VERSION_ONE) {
           kvDataLen = helper.kvDataLen;
           kvData = helper.kvData;
           errorCode = ktxTexture1_Create(&texinfo,
                                           KTX_TEXTURE_CREATE_ALLOC_STORAGE,
                                           (ktxTexture1**)&texture);
        } else {
            kvDataLen = helper.kvDataLenWriter_ktx2;
            kvData = helper.kvDataWriter_ktx2;
            texinfo.vkFormat
                = vkGetFormatFromOpenGLInternalFormat(texinfo.glInternalformat);
            errorCode = ktxTexture2_Create(&texinfo,
                                           KTX_TEXTURE_CREATE_ALLOC_STORAGE,
                                           (ktxTexture2**)&texture);
            texture->kvDataHead = helper.kvHash_ktx2;
        }
        if (KTX_SUCCESS != errorCode) {
            ADD_FAILURE() << "ktxTexture"
                          << (fv == KTX_FORMAT_VERSION_ONE ? "1" : "2")
                          << "_Create failed: "
                          << ktxErrorString(errorCode);
            return;
        }

        // Don't use the above helper.copyImagesToTexture because that is used
        // by various test cases which will compare their results against this.
        // A different code path here provides a small extra correctness check.
        std::vector<wthImageInfo>::const_iterator it = images.begin();
        for (ktx_uint32_t level = 0; level < texinfo.numLevels; level++) {
            ktx_uint32_t levelDepth = MAX(1, texinfo.baseDepth >> level);
            for (ktx_uint32_t layer = 0; layer < texinfo.numLayers; layer++) {
                ktx_uint32_t numImages = texinfo.numFaces == 6
                                       ? texinfo.numFaces : levelDepth;
                for (ktx_uint32_t faceSlice = 0; faceSlice < numImages; faceSlice++) {
                    ktxTexture_SetImageFromMemory(texture,
                                                  level, layer, faceSlice,
                                                  it->data, it->size);
                }
            }
            it++;
        }

        paddedImageDataSize = texture->dataSize;
        texture->kvData = kvData;
        texture->kvDataLen = kvDataLen;
        errorCode = ktxTexture_WriteToMemory(texture, &ktxMemFile,
                                             &ktxMemFileLen);
        if (KTX_SUCCESS != errorCode) {
            ADD_FAILURE() << "ktxTexture_WriteToMemory failed: "
                          << ktxErrorString(errorCode);
        }
    }

    ~ktxTextureTestBase() {
        delete ktxMemFile;
    }

    KTX_error_code
    iterCallback(int miplevel, int /*face*/,
                 int width, int /*height*/, int /*depth*/,
                 ktx_uint64_t faceLodSize,
                 void* pixels)
    {
        int expectedWidth = pixelSize >> miplevel;
        EXPECT_EQ(width, expectedWidth);
        EXPECT_EQ(faceLodSize, (uint64_t)(expectedWidth * expectedWidth * 4));
        EXPECT_EQ(memcmp(pixels, images[miplevel].data, images[miplevel].size),
                  0);
        iterCbCalls++;
        return KTX_SUCCESS;
    }

    static KTX_error_code
    iterCallback(int miplevel, int face,
                  int width, int height, int depth,
                  ktx_uint64_t faceLodSize,
                  void* pixels, void* userdata)
    {
        ktxTextureTestBase* fixture = (ktxTextureTestBase*)userdata;
        return fixture->iterCallback(miplevel, face, width, height, depth,
                                     faceLodSize, pixels);
    }

    TextureWriterTestHelper<component_type, numComponents, internalformat> helper;
    wthTexInfo& texinfo = helper.texinfo;
    ktxTextureCreateInfo& createInfo = helper.createInfo;
    unsigned char* kvData;
    unsigned int kvDataLen;

    ktx_uint8_t* ktxMemFile;
    ktx_size_t ktxMemFileLen;
    const int pixelSize;
    unsigned int mipLevels;
    unsigned int iterCbCalls;

    ktx_size_t paddedImageDataSize;
    ktx_size_t& imageDataSize = helper.imageDataSize;
    std::vector< std::vector < std::vector < std::vector<GLubyte>  > > >& imageData = helper.images;

    std::vector<wthImageInfo>& images = helper.imageList;
};

class ktxTexture1TestBase : public ktxTextureTestBase<GLubyte, 4, GL_RGBA8> {
  protected:
    ktxTexture1TestBase() : ktxTextureTestBase(KTX_FORMAT_VERSION_ONE) { }

    bool
    compareTexture(ktxTexture1* texture)
    {
        if (texture->glInternalformat != texinfo.glInternalformat)
            return false;
        if (texture->glBaseInternalformat != texinfo.glBaseInternalformat)
            return false;
        if (texture->glFormat != texinfo.glFormat)
            return false;
        if (texture->glType != texinfo.glType)
            return false;
        if (ktxTexture1_glTypeSize(texture) != texinfo.glTypeSize)
            return false;
        if (texture->baseWidth != texinfo.baseWidth)
            return false;
        if (texinfo.baseHeight == 0) {
            if (texture->baseHeight != 1)
                return false;
        } else if (texture->baseHeight != texinfo.baseHeight)
            return false;
        if (texinfo.baseDepth == 0) {
            if (texture->baseDepth != 1)
                return false;
        } else if (texture->baseDepth != texinfo.baseDepth)
            return false;
        if (texture->numFaces != texinfo.numFaces)
            return false;
        if (texture->numLevels != texinfo.numLevels)
            return false;
        return true;
    }
};


template<typename component_type,
         ktx_uint32_t numComponents,
         GLenum internalformat>
class ktxTexture2TestBase : public ktxTextureTestBase<component_type,
                                                      numComponents,
                                                      internalformat>
{
  protected:
    using ktxTextureTestBase<component_type, numComponents, internalformat>::helper;
    using ktxTextureTestBase<component_type, numComponents, internalformat>::texinfo;

    ktxTexture2TestBase() : ktxTextureTestBase<component_type, numComponents, internalformat>(KTX_FORMAT_VERSION_TWO) { }

    bool
    compareTexture(ktxTexture2* texture)
    {
        if (texture->vkFormat != (uint32_t)vkGetFormatFromOpenGLInternalFormat(helper.texinfo.glInternalformat))
            return false;
        if (texture->baseWidth != texinfo.baseWidth)
            return false;
        if (texinfo.baseHeight == 0) {
            if (texture->baseHeight != 1)
                return false;
        } else if (texture->baseHeight != texinfo.baseHeight)
            return false;
        if (texinfo.baseDepth == 0) {
            if (texture->baseDepth != 1)
                return false;
        } else if (texture->baseDepth != texinfo.baseDepth)
            return false;
        if (texture->numFaces != texinfo.numFaces)
            return false;
        if (texture->numLevels != texinfo.numLevels)
            return false;
        return true;
    }
};

class ktxTexture2_CreateTest : public ::testing::Test {
  protected:
    ktx_error_code_e create(VkFormat format,
                          ktx_uint32_t width = 16u,
                          ktx_uint32_t height = 16u,
                          ktx_uint32_t depth = 1u,
                          ktx_uint32_t dimensions = 2u,
                          ktx_uint32_t levels = 1u,
                          ktx_uint32_t layers = 1u,
                          ktx_uint32_t faces = 1u,
                          bool isArray = false,
                          bool generateMipmaps = false)
    {
        ktxTextureCreateInfo createInfo;
            createInfo.vkFormat = format;
            createInfo.baseWidth = width;
            createInfo.baseHeight = height;
            createInfo.baseDepth = depth;
            createInfo.numDimensions = dimensions;
            createInfo.numLevels = levels;
            createInfo.numLayers = layers;
            createInfo.numFaces = faces;
            createInfo.isArray = isArray;
            createInfo.generateMipmaps = generateMipmaps;

            return ktxTexture2_Create(&createInfo,
                                      KTX_TEXTURE_CREATE_ALLOC_STORAGE,
                                      &texture);
    }

    ~ktxTexture2_CreateTest() {
        ktxTexture_Destroy(ktxTexture(texture));
    }

    ktxTexture2* texture;
};

//----------------------------------------------------
// Template for base fixture for ktxTextureWrite tests.
//----------------------------------------------------

template<typename component_type, ktx_uint32_t numComponents,
         GLenum internalformat>
class ktxTexture1WriteTestBase : public ::testing::Test {
  public:
    using createFlags = typename WriterTestHelper<component_type, numComponents, internalformat>::createFlags;
    ktxTexture1WriteTestBase() { }

    void runTest(bool writeMetadata) {
        ktxTexture1* texture = 0;
        KTX_error_code result;
        ktx_uint8_t* ktxMemFile;
        ktx_size_t ktxMemFileLen;
        ktx_uint8_t* filePtr;

        result = ktxTexture1_Create(&helper.createInfo,
                                   KTX_TEXTURE_CREATE_ALLOC_STORAGE,
                                   &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                     << ktxErrorString(result);

        if (writeMetadata)
            ktxHashList_AddKVPair(&texture->kvDataHead, KTX_ORIENTATION_KEY,
                                  (unsigned int)strlen(helper.orientation) + 1,
                                  helper.orientation);

        result = helper.copyImagesToTexture(texture);
        ASSERT_TRUE(result == KTX_SUCCESS);

        EXPECT_EQ(helper.compareTexture1Images(texture->pData), true);
        result = ktxTexture1_WriteToMemory(texture, &ktxMemFile, &ktxMemFileLen);

        ASSERT_TRUE(result == KTX_SUCCESS) << "ktxTexture_WriteToMemory failed: "
                                           << ktxErrorString(result);
        EXPECT_EQ(memcmp(ktxMemFile, ktxId, sizeof(ktxId)), 0);
        EXPECT_EQ(helper.texinfo.compare((KTX_header*)ktxMemFile), true);

        // Check the metadata.
        filePtr = ktxMemFile + sizeof(KTX_header);
        if (writeMetadata) {
            EXPECT_EQ(memcmp(filePtr, helper.kvData, helper.kvDataLen), 0);
            filePtr += helper.kvDataLen;
        }
        // Check data pointer is properly aligned.
        EXPECT_EQ((intptr_t)filePtr & 0x3, 0);

        EXPECT_EQ(helper.compareRawImages(filePtr), true);

        delete ktxMemFile;
        ktxTexture1_Destroy(texture);
    }

    TextureWriterTestHelper<component_type, numComponents, internalformat> helper;
};

//---------------------------
// Actual test fixtures
//---------------------------

class ktxTexture_CreateTest : public ktxTexture1TestBase { };
class ktxTexture1_CreateTest : public ktxTexture1TestBase { };
class ktxTexture_KVDataTest : public ktxTexture1TestBase { };
class ktxTexture1_IterateLoadLevelFacesTest : public ktxTexture1TestBase { };
class ktxTexture1_IterateLevelFacesTest : public ktxTexture1TestBase { };
class ktxTexture1_LoadImageDataTest : public ktxTexture1TestBase { };

class ktxTexture1WriteTestRGBA8 : public ktxTexture1WriteTestBase<GLubyte, 4, GL_RGBA8> { };
class ktxTexture1WriteTestRGB8 : public ktxTexture1WriteTestBase<GLubyte, 3, GL_RGB8> { };
class ktxTexture1WriteTestRG16 : public ktxTexture1WriteTestBase<GLushort, 2, GL_RG16> { };

class ktxTexture2_IterateLoadLevelFacesTest : public ktxTexture2TestBase<GLubyte, 4, GL_RGBA8>  { };
class ktxTexture2_IterateLevelFacesTest : public ktxTexture2TestBase<GLubyte, 4, GL_RGBA8>  { };
class ktxTexture2_IterateLevelsTest : public ktxTexture2TestBase<GLubyte, 4, GL_RGBA8> { };
class ktxTexture2_LoadImageDataTest : public ktxTexture2TestBase<GLubyte, 4, GL_RGBA8> { };
class ktxTexture2_CreateCopyTest: public ktxTexture2TestBase<GLubyte, 4, GL_RGBA8> { };

/////////////////////////////////////////
// ktxTexture_Create tests
////////////////////////////////////////

TEST_F(ktxTexture1_CreateTest, InvalidValueOnNullParams) {
    ktxTexture* texture = 0;

    EXPECT_EQ(ktxTexture_CreateFromStdioStream(0, 0, &texture),
              KTX_INVALID_VALUE);
    EXPECT_EQ(ktxTexture_CreateFromNamedFile(0, 0, &texture),
              KTX_INVALID_VALUE);
    EXPECT_EQ(ktxTexture_CreateFromMemory(0, 0, 0, &texture),
              KTX_INVALID_VALUE);
    //EXPECT_EQ(ktxTexture_CreateFromStdioStream(0, 0, 0),
    //          KTX_INVALID_VALUE);
    EXPECT_EQ(ktxTexture_CreateFromNamedFile("foo", 0, 0),
              KTX_INVALID_VALUE);
    EXPECT_EQ(ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen, 0, 0),
              KTX_INVALID_VALUE);
}

TEST_F(ktxTexture_CreateTest, ConstructFromMemory) {
    ktxTexture* texture = 0;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             0, &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        EXPECT_EQ(texture->classId, ktxTexture1_c);
        EXPECT_EQ(compareTexture((ktxTexture1*)texture), true);
        EXPECT_EQ(texture->isCompressed, KTX_FALSE);
        EXPECT_EQ(texture->generateMipmaps, KTX_FALSE);
        EXPECT_EQ(texture->numDimensions, 2U);
        EXPECT_EQ(texture->numLayers, 1U);
        EXPECT_EQ(texture->isArray, KTX_FALSE);
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

TEST_F(ktxTexture1_CreateTest, ConstructFromMemory) {
    ktxTexture1* texture = 0;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture1_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             0, &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        EXPECT_EQ(compareTexture(texture), true);
        EXPECT_EQ(texture->isCompressed, KTX_FALSE);
        EXPECT_EQ(texture->generateMipmaps, KTX_FALSE);
        EXPECT_EQ(texture->numDimensions, 2U);
        EXPECT_EQ(texture->numLayers, 1U);
        EXPECT_EQ(texture->isArray, KTX_FALSE);
        if (texture)
            ktxTexture1_Destroy(texture);
    }
}

TEST_F(ktxTexture1_CreateTest, CreateEmpty) {
    ktxTexture1* texture = 0;
    KTX_error_code result;

    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                &texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    if (texture)
        ktxTexture1_Destroy(texture);
}

TEST_F(ktxTexture1_CreateTest, InvalidValueTooManyMipLevels) {
    ktxTexture1* texture = 0;

    createInfo.numLevels += 1;

    EXPECT_EQ(ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE, &texture),
              KTX_INVALID_OPERATION);
}

TEST_F(ktxTexture1_CreateTest, InvalidOpOnSetImagesNoStorage) {
    ktxTexture1* texture = 0;
    KTX_error_code result;

    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                               &texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);

    // Type RGBA UNSIGNED_BYTE -> *4
    ktx_size_t imageBytes = imageData[0].size() * 4;
    ktx_uint8_t* imageDataPtr = (ktx_uint8_t*)(&imageData[0].front());
    // Allocate the image data and initialize it to a color.
    EXPECT_EQ(ktxTexture1_SetImageFromMemory(texture, 0, 0, 0,
                                             imageDataPtr,
                                             imageBytes),
              KTX_INVALID_OPERATION);
    ASSERT_TRUE(result == KTX_SUCCESS);

    if (texture)
        ktxTexture1_Destroy(texture);
}

TEST_F(ktxTexture1_CreateTest, CreateEmptyAndSetImages) {
    ktxTexture1* texture = 0;
    KTX_error_code result;

    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_ALLOC_STORAGE,
                                &texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);

    result = helper.copyImagesToTexture(texture);
    ASSERT_TRUE(result == KTX_SUCCESS);
    // imageData is an RGBA texture so no rounding is necessary and we can
    // use this simple comparison.
    EXPECT_EQ(helper.compareTexture1Images(texture->pData), true);

    if (texture)
        ktxTexture1_Destroy(texture);
}

TEST_F(ktxTexture1_CreateTest, CreateEmptySetImagesWriteToMemory) {
    ktxTexture1* texture = 0;
    KTX_error_code result;
    ktx_uint8_t* testMemFile;
    ktx_size_t testMemFileLen;
    char orientation[10];

    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_ALLOC_STORAGE,
                                &texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);

    snprintf(orientation, sizeof(orientation), KTX_ORIENTATION2_FMT,
             'r', 'd');
    ktxHashList_AddKVPair(&texture->kvDataHead, KTX_ORIENTATION_KEY,
                          (unsigned int)strlen(orientation) + 1,
                          orientation);
    result = helper.copyImagesToTexture(texture);
    ASSERT_TRUE(result == KTX_SUCCESS);
    EXPECT_EQ(helper.compareTexture1Images(texture->pData), true);
    EXPECT_EQ(ktxTexture1_WriteToMemory(texture, &testMemFile, &testMemFileLen),
              KTX_SUCCESS);
    EXPECT_EQ(testMemFileLen, ktxMemFileLen);
    EXPECT_EQ(memcmp(testMemFile, ktxMemFile, ktxMemFileLen), 0);

    if (texture)
        ktxTexture1_Destroy(texture);
}

/////////////////////////////////////////
// ktxTexture2_Create tests
////////////////////////////////////////

TEST_F(ktxTexture2_CreateTest, E5B9G9R9) {
    ktx_error_code_e result = create(VK_FORMAT_E5B9G9R9_UFLOAT_PACK32);
    EXPECT_EQ(result, KTX_SUCCESS);
}

/////////////////////////////////////////
// ktxTexture_KVData tests
////////////////////////////////////////

TEST_F(ktxTexture_KVDataTest, KVDataDeserialized) {
    ktxTexture* texture = 0;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             0,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->kvData == NULL) << "Raw KVData should not be loaded";
        ASSERT_TRUE(texture->kvDataHead != NULL) << "KVData not deserialized";

        char* pValue;
        ktx_uint32_t valueLen;
        char s, t;
        result = ktxHashList_FindValue(&texture->kvDataHead,
                                       KTX_ORIENTATION_KEY,
                                       &valueLen, (void**)&pValue);
        EXPECT_EQ(result, KTX_SUCCESS);
        EXPECT_EQ(sscanf(pValue, /*valueLen,*/ KTX_ORIENTATION2_FMT, &s, &t), 2);
        EXPECT_EQ(s,'r');
        EXPECT_EQ(t, 'd');
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

TEST_F(ktxTexture_KVDataTest, LoadRawKVData) {
    ktxTexture* texture = 0;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_RAW_KVDATA_BIT,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
        << ktxErrorString(result);
        ASSERT_TRUE(texture->kvData != NULL) << "Raw KVData not loaded";
        ASSERT_TRUE(texture->kvDataHead == NULL) << "KVData should not be deserialized";
        EXPECT_EQ(texture->kvDataLen, kvDataLen) << "Length of KV data incorrect";
        EXPECT_EQ(memcmp(texture->kvData, kvData, kvDataLen), 0);
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

TEST_F(ktxTexture_KVDataTest, SkipKVData) {
    ktxTexture* texture = 0;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_SKIP_KVDATA_BIT,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->kvData == NULL) << "Raw KVData should not be loaded";
        ASSERT_TRUE(texture->kvDataHead == NULL) << "KVData should not be deserialized";
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

/////////////////////////////////////////
// ktxTexture_IterateLoadLevelFaces tests
////////////////////////////////////////

TEST_F(ktxTexture1_IterateLoadLevelFacesTest, InvalidValueOnNullCallback) {
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktxTexture1_IterateLoadLevelFacesTest* fixture = this;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             0, &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);

        EXPECT_EQ(ktxTexture_IterateLoadLevelFaces(texture, 0, fixture),
                  KTX_INVALID_VALUE);
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

TEST_F(ktxTexture1_IterateLoadLevelFacesTest, InvalidOpWhenDataAlreadyLoaded) {
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktxTexture1_IterateLoadLevelFacesTest* fixture = this;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";
        EXPECT_EQ(ktxTexture_IterateLoadLevelFaces(texture, iterCallback, fixture),
                  KTX_INVALID_OPERATION);
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

TEST_F(ktxTexture1_IterateLoadLevelFacesTest, IterateImages) {
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktxTexture1_IterateLoadLevelFacesTest* fixture = this;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             0, &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);

        EXPECT_EQ(ktxTexture_IterateLoadLevelFaces(texture, iterCallback, fixture),
                  KTX_SUCCESS);
        EXPECT_EQ(iterCbCalls, mipLevels)
                  << "No. of calls to iterCallback differs from number of mip levels";
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

/////////////////////////////////////////
// ktxTexture_IterateLevelFaces tests
////////////////////////////////////////

TEST_F(ktxTexture1_IterateLevelFacesTest, InvalidValueOnNullCallback) {
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktxTexture1_IterateLevelFacesTest* fixture = this;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";
        EXPECT_EQ(ktxTexture_IterateLevelFaces(texture, 0, fixture),
                  KTX_INVALID_VALUE);
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

TEST_F(ktxTexture1_IterateLevelFacesTest, IterateImages) {
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktxTexture1_IterateLevelFacesTest* fixture = this;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);

        EXPECT_EQ(ktxTexture_IterateLevelFaces(texture, iterCallback, fixture),
                  KTX_SUCCESS);
        EXPECT_EQ(iterCbCalls, mipLevels)
                  << "No. of calls to iterCallback differs from number of mip levels";
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

TEST_F(ktxTexture2_IterateLevelFacesTest, InvalidValueOnNullCallback) {
    ktxTexture* texture;
    KTX_error_code result;
    ktxTexture2_IterateLevelFacesTest* fixture = this;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                             &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";
        EXPECT_EQ(ktxTexture_IterateLevelFaces(texture, 0, fixture),
                  KTX_INVALID_VALUE);
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

TEST_F(ktxTexture2_IterateLevelFacesTest, IterateImages) {
    ktxTexture* texture;
    KTX_error_code result;
    ktxTexture2_IterateLevelFacesTest* fixture = this;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                             &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);

        EXPECT_EQ(ktxTexture_IterateLevelFaces(texture, iterCallback, fixture),
                  KTX_SUCCESS);
        EXPECT_EQ(iterCbCalls, mipLevels)
                  << "No. of calls to iterCallback differs from number of mip levels";
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

/////////////////////////////////////////
// ktxTexture_IterateLevels tests
////////////////////////////////////////

TEST_F(ktxTexture2_IterateLevelsTest, InvalidValueOnNullCallback) {
    ktxTexture* texture;
    KTX_error_code result;
    ktxTexture2_IterateLevelsTest* fixture = this;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                             &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";
        EXPECT_EQ(ktxTexture_IterateLevels(texture, 0, fixture),
                  KTX_INVALID_VALUE);
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

TEST_F(ktxTexture2_IterateLevelsTest, IterateLevels) {
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktxTexture2_IterateLevelsTest* fixture = this;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);

        EXPECT_EQ(ktxTexture_IterateLevelFaces(texture, iterCallback, fixture),
                  KTX_SUCCESS);
        EXPECT_EQ(iterCbCalls, mipLevels)
                  << "No. of calls to iterCallback differs from number of mip levels";
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

/////////////////////////////////////////
// ktxTexture_LoadImageData tests
////////////////////////////////////////

TEST_F(ktxTexture1_LoadImageDataTest, InvalidOpWhenDataAlreadyLoaded) {
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktx_uint8_t* buf;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";
        buf = new ktx_uint8_t[paddedImageDataSize];
        EXPECT_EQ(ktxTexture_LoadImageData(texture, buf, paddedImageDataSize),
                  KTX_INVALID_OPERATION);
        if (texture)
            ktxTexture_Destroy(texture);
        delete[] buf;
    }
}

TEST_F(ktxTexture1_LoadImageDataTest, InvalidOpWhenDataAlreadyLoadedToExternal) {
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktx_uint8_t* buf;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             0,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData == NULL) << "Image data must not be loaded";
        buf = new ktx_uint8_t[paddedImageDataSize];
        EXPECT_EQ(ktxTexture_LoadImageData(texture, buf, paddedImageDataSize),
                  KTX_SUCCESS);
        EXPECT_EQ(ktxTexture_LoadImageData(texture, buf, paddedImageDataSize),
                  KTX_INVALID_OPERATION);
        if (texture)
            ktxTexture_Destroy(texture);
        delete[] buf;
    }
}

TEST_F(ktxTexture1_LoadImageDataTest, LoadImageDataInternal) {
    ktxTexture* texture = 0;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";
        EXPECT_EQ(paddedImageDataSize, ktxTexture_GetDataSize(texture));
        EXPECT_EQ(helper.compareTexture1Images(ktxTexture_GetData(texture)), true);
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

TEST_F(ktxTexture1_LoadImageDataTest, LoadImageDataExternal) {
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktx_uint8_t* buf;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             0,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        buf = new ktx_uint8_t[paddedImageDataSize];
        EXPECT_EQ(ktxTexture_LoadImageData(texture, buf, paddedImageDataSize),
                  KTX_SUCCESS);
        EXPECT_EQ(paddedImageDataSize, ktxTexture_GetDataSize(texture));
        EXPECT_EQ(helper.compareTexture1Images(buf), true);
        if (texture)
            ktxTexture_Destroy(texture);
        delete[] buf;
    }
}

TEST_F(ktxTexture2_LoadImageDataTest, InvalidOpWhenDataAlreadyLoaded) {
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktx_uint8_t* buf;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";
        buf = new ktx_uint8_t[paddedImageDataSize];
        EXPECT_EQ(ktxTexture_LoadImageData(texture, buf, paddedImageDataSize),
                  KTX_INVALID_OPERATION);
        if (texture)
            ktxTexture_Destroy(texture);
        delete[] buf;
    }
}

TEST_F(ktxTexture2_LoadImageDataTest, InvalidOpWhenDataAlreadyLoadedToExternal) {
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktx_uint8_t* buf;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             0,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData == NULL) << "Image data must not be loaded";
        buf = new ktx_uint8_t[paddedImageDataSize];
        EXPECT_EQ(ktxTexture_LoadImageData(texture, buf, paddedImageDataSize),
                  KTX_SUCCESS);
        EXPECT_EQ(ktxTexture_LoadImageData(texture, buf, paddedImageDataSize),
                  KTX_INVALID_OPERATION);
        if (texture)
            ktxTexture_Destroy(texture);
        delete[] buf;
    }
}

TEST_F(ktxTexture2_LoadImageDataTest, LoadImageDataInternal) {
    ktxTexture* texture = 0;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";
        EXPECT_EQ(paddedImageDataSize, ktxTexture_GetDataSize(texture));
        EXPECT_EQ(helper.compareTexture2Images(ktxTexture_GetData(texture)), true);
        if (texture)
            ktxTexture_Destroy(texture);
    }
}

TEST_F(ktxTexture2_LoadImageDataTest, LoadImageDataExternal) {
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktx_uint8_t* buf;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             0,
                                             &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        buf = new ktx_uint8_t[paddedImageDataSize];
        EXPECT_EQ(ktxTexture_LoadImageData(texture, buf, paddedImageDataSize),
                  KTX_SUCCESS);
        EXPECT_EQ(paddedImageDataSize, ktxTexture_GetDataSize(texture));
        EXPECT_EQ(helper.compareTexture2Images(buf), true);
        if (texture)
            ktxTexture_Destroy(texture);
        delete[] buf;
    }
}

/////////////////////////////////////////////
// ktxTexture2_CreateCopyTest
////////////////////////////////////////////

TEST_F(ktxTexture2_CreateCopyTest, CreateCopy) {
    ktxTexture2* texture = 0;
    ktxTexture2* copyTexture = 0;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                             0,
                                             (ktxTexture**)&texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        result = ktxTexture2_CreateCopy(texture, &copyTexture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(copyTexture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);

        EXPECT_EQ(compareTexture(copyTexture), true);
        EXPECT_EQ(memcmp(texture->pData, copyTexture->pData, texture->dataSize),
                  0);
        EXPECT_EQ(memcmp(texture->_protected, copyTexture->_protected,
                         sizeof(ktxTexture_protected)), 0);
        ktx_size_t privateSize = sizeof(ktxTexture2_private)
                               + sizeof(ktxLevelIndexEntry)
                               * (texture->numLevels - 1);
        EXPECT_EQ(memcmp(texture->_private, copyTexture->_private,
                         privateSize), 0);

        if (texture)
            ktxTexture_Destroy((ktxTexture*)texture);
        if (copyTexture)
            ktxTexture_Destroy((ktxTexture*)copyTexture);
    }
}

/////////////////////////////////////////////
// TestCreateInfo for size and offset tests.
////////////////////////////////////////////

class TestCreateInfo : public ktxTextureCreateInfo {
  public:
    TestCreateInfo() : TestCreateInfo(16) { }

    TestCreateInfo(ktx_uint32_t pixelSize)
    : TestCreateInfo(pixelSize, pixelSize, 1) { }

    TestCreateInfo(ktx_uint32_t width, ktx_uint32_t height, ktx_uint32_t depth)
    : TestCreateInfo(width, height, depth, 2, GL_RGBA8,
                     VK_FORMAT_R8G8B8A8_UNORM, KTX_FALSE, 1, 1) { }

    TestCreateInfo(ktx_uint32_t width, ktx_uint32_t height, ktx_uint32_t depth,
                   ktx_uint32_t dimensions, ktx_uint32_t internalformat,
                   ktx_uint32_t vkformat, ktx_bool_t array, ktx_uint32_t faces,
                   ktx_uint32_t layers) {
        baseWidth = width;
        baseHeight = height;
        baseDepth = depth;
        numDimensions = dimensions;
        generateMipmaps = KTX_FALSE;
        glInternalformat = internalformat;
        vkFormat = vkformat;
        isArray = array;
        numFaces = faces;
        numLayers = layers;
        numLevels = levelsFromSize(width, height, depth);
    };

    static ktx_uint32_t
    levelsFromSize(ktx_uint32_t width, ktx_uint32_t height, ktx_uint32_t depth) {
        ktx_uint32_t mipLevels;
        ktx_uint32_t max_dim = MAX(MAX(width, height), depth);
        for (mipLevels = 1; max_dim != 1; mipLevels++, max_dim >>= 1) { }
        return mipLevels;
    }
};

/////////////////////////////////////////
// ktxTexture_calcImageSize tests
////////////////////////////////////////

TEST(ktxTexture_calcImageSize, ImageSizeAtEachLevelRGBA2D) {
    ktxTexture* texture = 0;
    TestCreateInfo createInfo;
    KTX_error_code result;
    // Sizes for 16x16, 5 level RGBA8 texture.
    // level 0 ... level 4
    ktx_uint32_t ktx1sizes[] = {1024, 256, 64, 16, 4};
    ktx_uint32_t ktx2sizes[] = {1024, 256, 64, 16, 4};

    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                (ktxTexture1**)&texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    for (ktx_uint32_t i = 0; i < createInfo.numLevels; i++) {
        ktx_size_t imageSize;
        imageSize = ktxTexture_calcImageSize(texture, i,
                                             KTX_FORMAT_VERSION_ONE);
        EXPECT_EQ(imageSize, ktx1sizes[i]);
        imageSize = ktxTexture_calcImageSize(texture, i,
                                             KTX_FORMAT_VERSION_TWO);
        EXPECT_EQ(imageSize, ktx2sizes[i]);
    }
    if (texture)
        ktxTexture_Destroy(texture);
}

TEST(ktxTexture_calcImageSize, ImageSizeAtEachLevelRGB2D) {
    ktxTexture* texture = 0;
    TestCreateInfo createInfo(9, 9, 1, 2, GL_RGB8,
                              VK_FORMAT_R8G8B8_UNORM, KTX_FALSE, 1, 1);
    KTX_error_code result;
    // Sizes for 9x9, 4 level RGB8 texture.
    // level 0 ... level 4
    ktx_uint32_t ktx1sizes[] = {28*9, 12*4, 8*2, 4*1};
    ktx_uint32_t ktx2sizes[] = {27*9, 12*4, 6*2, 3*1};

    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                (ktxTexture1**)&texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    for (ktx_uint32_t i = 0; i < createInfo.numLevels; i++) {
        ktx_size_t imageSize;
        imageSize = ktxTexture_calcImageSize(texture, i,
                                             KTX_FORMAT_VERSION_ONE);
        EXPECT_EQ(imageSize, ktx1sizes[i]);
        imageSize = ktxTexture_calcImageSize(texture, i,
                                             KTX_FORMAT_VERSION_TWO);
        EXPECT_EQ(imageSize, ktx2sizes[i]);
    }
    if (texture)
        ktxTexture_Destroy(texture);
}

/////////////////////////////////////////
// ktxTexture_calcLevelSize tests
////////////////////////////////////////

TEST(ktxTexture_calcLevelSize, SizeOfEachLevelRGBA2D) {
    ktxTexture* texture = 0;
    TestCreateInfo createInfo;
    KTX_error_code result;
    // Sizes for 16x16, 5 level RGBA8 texture.
    // level 0 ... level 4
    ktx_uint32_t ktx1sizes[] = {1024, 256, 64, 16, 4};
    ktx_uint32_t ktx2sizes[] = {1024, 256, 64, 16, 4};

    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                (ktxTexture1**)&texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    for (ktx_uint32_t i = 0; i < createInfo.numLevels; i++) {
        ktx_size_t levelSize;
        levelSize = ktxTexture_calcLevelSize(texture, i,
                                             KTX_FORMAT_VERSION_ONE);
        EXPECT_EQ(levelSize, ktx1sizes[i]);
        levelSize = ktxTexture_calcLevelSize(texture, i,
                                             KTX_FORMAT_VERSION_TWO);
        EXPECT_EQ(levelSize, ktx2sizes[i]);
    }
    if (texture)
        ktxTexture_Destroy(texture);
}

TEST(ktxTexture_calcLevelSize, SizeOfEachLevelRGB2D) {
    ktxTexture* texture = 0;
    TestCreateInfo createInfo(9, 9, 1, 2, GL_RGB8,
                              VK_FORMAT_R8G8B8_UNORM, KTX_FALSE, 1, 1);
    KTX_error_code result;
    // Sizes for 9x9, 4 level RGB8 texture.
    // level 0 ... level 4
    ktx_uint32_t ktx1sizes[] = {28*9, 12*4, 8*2, 4*1};
    ktx_uint32_t ktx2sizes[] = {27*9, 12*4, 6*2, 3*1};

    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                (ktxTexture1**)&texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    for (ktx_uint32_t i = 0; i < createInfo.numLevels; i++) {
        ktx_size_t levelSize;
        levelSize = ktxTexture_calcLevelSize(texture, i,
                                             KTX_FORMAT_VERSION_ONE);
        EXPECT_EQ(levelSize, ktx1sizes[i]);
        levelSize = ktxTexture_calcLevelSize(texture, i,
                                             KTX_FORMAT_VERSION_TWO);
        EXPECT_EQ(levelSize, ktx2sizes[i]);
    }
    if (texture)
        ktxTexture_Destroy(texture);
}

/////////////////////////////////////////
// ktxTexture_calcLevelOffset tests
////////////////////////////////////////

TEST(ktxTexture_calcLevelOffset, OffsetOfEachLevelRGBA2D) {
    ktxTexture1* ktx1texture = 0;
    ktxTexture2* ktx2texture = 0;
    TestCreateInfo createInfo;
    KTX_error_code result;
    // Offsets for 16x16, 5 level RGBA8 texture.
    // KTX 1: level 0 ... level 4
    ktx_uint32_t ktx1offsets[] = {0, 1024, 1024+256, 1024+256+64, 1024+256+64+16};
    // KTX 2: level 0 ... level 4 with mip padding to a 4 byte alignment.
    ktx_uint32_t ktx2offsets[] = {4+16+64+256, 4+16+64, 4+16, 4, 0};

    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                &ktx1texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(ktx1texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    result = ktxTexture2_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                &ktx2texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(ktx2texture != NULL) << "ktxTexture2_Create failed: "
                                 << ktxErrorString(result);
    for (ktx_uint32_t i = 0; i < createInfo.numLevels; i++) {
        ktx_size_t levelOffset;
        levelOffset = ktxTexture1_calcLevelOffset(ktx1texture, i);
        EXPECT_EQ(levelOffset, ktx1offsets[i]);
        levelOffset = ktxTexture2_calcLevelOffset(ktx2texture, i);
        EXPECT_EQ(levelOffset, ktx2offsets[i]);
    }
    if (ktx1texture)
        ktxTexture_Destroy(ktxTexture(ktx1texture));
    if (ktx2texture)
        ktxTexture_Destroy(ktxTexture(ktx2texture));
}

TEST(ktxTexture_calcLevelOffset, OffsetOfEachLevelRGB2D) {
    ktxTexture1* ktx1texture = 0;
    ktxTexture2* ktx2texture = 0;
    TestCreateInfo createInfo(9, 9, 1, 2, GL_RGB8,
                              VK_FORMAT_R8G8B8_UNORM, KTX_FALSE, 1, 1);
    KTX_error_code result;
    // Offsets for 9x9, 4 level RGB8 texture.
    // KTX 1: level 0 ... level 4
    ktx_uint32_t ktx1offsets[] = {0, 28*9, 28*9+12*4, 28*9+12*4+8*2};
    // KTX 2: level 0 ... level 4 with mip padding to a 12 byte alignment.
    ktx_uint32_t ktx2offsets[] = {12*4+24, 6*2+12, 3*1+9, 0};

    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                &ktx1texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(ktx1texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    result = ktxTexture2_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                &ktx2texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(ktx2texture != NULL) << "ktxTexture2_Create failed: "
                                 << ktxErrorString(result);

    for (ktx_uint32_t i = 0; i < createInfo.numLevels; i++) {
        ktx_size_t levelOffset;
        levelOffset = ktxTexture1_calcLevelOffset(ktx1texture, i);
        EXPECT_EQ(levelOffset, ktx1offsets[i]);
        levelOffset = ktxTexture2_calcLevelOffset(ktx2texture, i);
        EXPECT_EQ(levelOffset, ktx2offsets[i]);
    }
    if (ktx1texture)
        ktxTexture_Destroy(ktxTexture(ktx1texture));
    if (ktx2texture)
        ktxTexture_Destroy(ktxTexture(ktx2texture));
}

TEST(ktxTexture_calcLevelOffset, OffsetOfEachLevelD16_UNORM_S8_UINT) {
    ktxTexture2* ktx2texture = 0;
    TestCreateInfo createInfo(9, 9, 1, 2, 0,
                              VK_FORMAT_D16_UNORM_S8_UINT, KTX_FALSE, 1, 1);
    KTX_error_code result;
    // Offsets for 9x9, 4 level  texture.
    // KTX 2: level 0 ... level 4 with mip padding to a 4 byte alignment.
    ktx_uint32_t ktx2offsets[] = {4+16+64, 4+16, 4, 0};

    result = ktxTexture2_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                &ktx2texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(ktx2texture != NULL) << "ktxTexture2_Create failed: "
                                 << ktxErrorString(result);

    for (ktx_uint32_t i = 0; i < createInfo.numLevels; i++) {
        ktx_size_t levelOffset;
        levelOffset = ktxTexture2_calcLevelOffset(ktx2texture, i);
        EXPECT_EQ(levelOffset, ktx2offsets[i]);
    }
    if (ktx2texture)
        ktxTexture_Destroy(ktxTexture(ktx2texture));
}

TEST(ktxTexture_calcLevelOffset, OffsetOfEachLevelD32_SFLOAT_S8_UINT) {
    ktxTexture2* ktx2texture = 0;
    TestCreateInfo createInfo(9, 9, 1, 2, 0,
                              VK_FORMAT_D32_SFLOAT_S8_UINT, KTX_FALSE, 1, 1);
    KTX_error_code result;
    // Offsets for 9x9, 4 level  texture.
    // KTX 2: level 0 ... level 4 with mip padding to an 8 byte alignment.
    ktx_uint32_t ktx2offsets[] = {8+32+128, 8+32, 8, 0};

    result = ktxTexture2_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                &ktx2texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(ktx2texture != NULL) << "ktxTexture2_Create failed: "
                                 << ktxErrorString(result);

    for (ktx_uint32_t i = 0; i < createInfo.numLevels; i++) {
        ktx_size_t levelOffset;
        levelOffset = ktxTexture2_calcLevelOffset(ktx2texture, i);
        EXPECT_EQ(levelOffset, ktx2offsets[i]);
    }
    if (ktx2texture)
        ktxTexture_Destroy(ktxTexture(ktx2texture));
}

/////////////////////////////////////////
// ktxTexture_GetImageOffset tests
////////////////////////////////////////

TEST(ktxTexture_GetImageOffsetTest, InvalidOpOnLevelFaceLayerTooBig) {
    ktxTexture* texture = 0;
    TestCreateInfo createInfo;
    KTX_error_code result;
    ktx_size_t offset;

    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                (ktxTexture1**)&texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, createInfo.numLevels, 0, 0, &offset),
              KTX_INVALID_OPERATION);
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, 0, createInfo.numLayers, 0, &offset),
              KTX_INVALID_OPERATION);
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, 0, 0, createInfo.numFaces, &offset),
              KTX_INVALID_OPERATION);
    if (texture)
        ktxTexture_Destroy(texture);
}

TEST(ktxTexture_GetImageOffsetTest, ImageOffsetLevel) {
    //using createFlagBits = typename WriterTestHelper<GLubyte, 4, GL_RGBA8>::createFlagBits;
    TextureWriterTestHelper<GLubyte, 4, GL_RGBA8> helper;

    helper.resize(createFlagBits::eMipmapped, 1, 1, 2, 16, 16, 1);
    ktxTexture* texture = 0;
    KTX_error_code result;
    ktx_size_t expectedOffset, imageSize, offset;

    result = ktxTexture1_Create(&helper.createInfo,
                               KTX_TEXTURE_CREATE_NO_STORAGE,
                               (ktxTexture1**)&texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, 0, 0, 0, &offset),
              KTX_SUCCESS);
    EXPECT_EQ(offset, 0U);
    // GL_RGBA8 is 1 x 4 bytes.
    imageSize = helper.createInfo.baseWidth
                * helper.createInfo.baseHeight * 1 * 4;
    expectedOffset = imageSize;
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, 1, 0, 0, &offset),
              KTX_SUCCESS);
    EXPECT_EQ(offset, expectedOffset);
    // The image used to calculate imageDataSize has the same dimensions and
    // internalformat as those specified by createInfo.
    expectedOffset = helper.imageDataSize - 4;
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, helper.createInfo.numLevels - 1, 0, 0, &offset),
              KTX_SUCCESS);
    EXPECT_EQ(offset, expectedOffset);
    if (texture)
        ktxTexture_Destroy(texture);
}

TEST(ktxTexture_GetImageOffsetTest, ImageOffsetWithRowPadding) {
    ktxTexture* texture = 0;
    TestCreateInfo createInfo;
    KTX_error_code result;
    ktx_size_t expectedOffset, imageSize, offset;
    ktx_uint32_t rowBytes, rowRounding;

    // Pick type and size that requires row padding for KTX_GL_UNPACK_ALIGNMENT.
    createInfo.glInternalformat = GL_RGB8;
    createInfo.baseWidth = 9;
    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                (ktxTexture1**)&texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    rowBytes = 9 * 3;
    rowRounding = ROUNDING(rowBytes);
    imageSize = (rowBytes + rowRounding) * texture->baseHeight;
    expectedOffset = imageSize;
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, 1, 0, 0, &offset),
              KTX_SUCCESS);
    EXPECT_EQ(offset, expectedOffset);

    expectedOffset = 0;
    for (ktx_uint32_t i = 0; i < texture->numLevels - 1; i++) {
        ktx_uint32_t levelWidth = MAX(1, texture->baseWidth >> i);
        ktx_uint32_t levelHeight = MAX(1, texture->baseHeight >> i);

        int levelRowBytes = levelWidth * 3;
        rowRounding = ROUNDING(levelRowBytes);
        levelRowBytes += rowRounding;
        imageSize = levelRowBytes * levelHeight;
        expectedOffset += imageSize;
    }
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, createInfo.numLevels - 1, 0, 0, &offset),
              KTX_SUCCESS);
    EXPECT_EQ(offset, expectedOffset);
    if (texture)
        ktxTexture_Destroy(texture);
}

TEST(ktxTexture_GetImageOffsetTest, ImageOffsetArray) {
    ktxTexture* texture = 0;
    TestCreateInfo createInfo;
    KTX_error_code result;
    ktx_size_t expectedOffset, offset;
    ktx_uint32_t rowBytes, rowRounding, imageSize, layerSize;
    ktx_uint32_t levelWidth, levelHeight, levelImageSize, levelRowBytes;

    createInfo.glInternalformat = GL_RGB8;
    createInfo.baseWidth = 9;
    createInfo.numLayers = 3;
    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                (ktxTexture1**)&texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    rowBytes = 9 * 3;
    rowRounding = ROUNDING(rowBytes);
    imageSize = (rowBytes + rowRounding) * createInfo.baseHeight;
    layerSize = imageSize * texture->numFaces;
    expectedOffset = layerSize * texture->numLayers;
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, 1, 0, 0, &offset),
              KTX_SUCCESS);
    EXPECT_EQ(offset, expectedOffset);
    levelWidth = MAX(1, texture->baseWidth >> 1);
    levelHeight = MAX(1, texture->baseHeight >> 1);
    levelRowBytes = levelWidth * 3;
    rowRounding = ROUNDING(levelRowBytes);
    levelRowBytes += rowRounding;
    levelImageSize = levelRowBytes * levelHeight;
    expectedOffset += levelImageSize * 2;
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, 1, 2, 0, &offset),
              KTX_SUCCESS);
    EXPECT_EQ(offset, expectedOffset);
    if (texture)
        ktxTexture_Destroy(texture);
}

TEST(ktxTexture_GetImageOffsetTest, ImageOffsetFace) {
    ktxTexture* texture = 0;
    TestCreateInfo createInfo;
    KTX_error_code result;
    ktx_size_t expectedOffset, offset;
    ktx_uint32_t rowBytes, rowRounding, imageSize, layerSize;
    ktx_uint32_t levelWidth, levelHeight, levelImageSize, levelRowBytes;

    createInfo.glInternalformat = GL_RGB8;
    createInfo.baseWidth = 9;
    createInfo.baseHeight = 9;
    createInfo.numLevels = 4;
    createInfo.numLayers = 1;
    createInfo.numFaces = 6;
    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                (ktxTexture1**)&texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    rowBytes = 9 * 3;
    rowRounding = ROUNDING(rowBytes);
    imageSize = (rowBytes + rowRounding) * texture->baseHeight;
    layerSize = imageSize * texture->numFaces;
    expectedOffset = imageSize * 4;
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, 0, 0, 4, &offset),
              KTX_SUCCESS);
    EXPECT_EQ(offset, expectedOffset);
    levelWidth = MAX(1, texture->baseWidth >> 1);
    levelHeight = MAX(1, texture->baseHeight >> 1);
    levelRowBytes = levelWidth * 3;
    rowRounding = ROUNDING(levelRowBytes);
    levelRowBytes += rowRounding;
    levelImageSize = levelRowBytes * levelHeight;
    expectedOffset = layerSize + levelImageSize * 3;
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, 1, 0, 3, &offset),
              KTX_SUCCESS);
    EXPECT_EQ(offset, expectedOffset);
    if (texture)
        ktxTexture_Destroy(texture);
}

TEST(ktxTexture_GetImageOffsetTest, ImageOffsetArrayFace) {
    ktxTexture* texture = 0;
    TestCreateInfo createInfo;
    KTX_error_code result;
    ktx_size_t expectedOffset, offset;
    ktx_uint32_t rowBytes, rowRounding, imageSize, layerSize;
    ktx_uint32_t levelWidth, levelHeight, levelImageSize, levelRowBytes;

    createInfo.glInternalformat = GL_RGB8;
    createInfo.baseWidth = 9;
    createInfo.baseWidth = 9;
    createInfo.baseHeight = 9;
    createInfo.numLevels = 4;
    createInfo.numLayers = 3;
    createInfo.numFaces = 6;
    result = ktxTexture1_Create(&createInfo, KTX_TEXTURE_CREATE_NO_STORAGE,
                                (ktxTexture1**)&texture);
    EXPECT_EQ(result, KTX_SUCCESS);
    ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                 << ktxErrorString(result);
    rowBytes = 9 * 3;
    rowRounding = ROUNDING(rowBytes);
    imageSize = (rowBytes + rowRounding) * createInfo.baseHeight;
    layerSize = imageSize * texture->numFaces;
    expectedOffset = layerSize * createInfo.numLayers;
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, 1, 0, 0, &offset),
              KTX_SUCCESS);
    EXPECT_EQ(offset, expectedOffset);
    levelWidth = MAX(1, createInfo.baseWidth >> 1);
    levelHeight = MAX(1, createInfo.baseHeight >> 1);
    levelRowBytes = levelWidth * 3;
    rowRounding = ROUNDING(levelRowBytes);
    levelRowBytes += rowRounding;
    levelImageSize = levelRowBytes * levelHeight;
    expectedOffset += levelImageSize * texture->numFaces * 2;
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, 1, 2, 0, &offset),
              KTX_SUCCESS);
    EXPECT_EQ(offset, expectedOffset);
    expectedOffset += levelImageSize * 3; // 3 faces
    EXPECT_EQ(ktxTexture_GetImageOffset(texture, 1, 2, 3, &offset),
              KTX_SUCCESS);
    EXPECT_EQ(offset, expectedOffset);
    if (texture)
        ktxTexture_Destroy(texture);
}

/////////////////////////////////////////
// ktxTexture_Write tests
////////////////////////////////////////

TEST_F(ktxTexture1WriteTestRGB8, Write1D) {
    helper.resize(createFlagBits::eNone, 1, 1, 1, 32, 1, 1);
    runTest(false);
}

TEST_F(ktxTexture1WriteTestRGB8, Write1DNeedsPadding) {
    helper.resize(createFlagBits::eNone, 1, 1, 1, 9, 1, 1);
    runTest(false);
}

TEST_F(ktxTexture1WriteTestRGBA8, Write1DMipmap) {
    helper.resize(createFlagBits::eMipmapped, 1, 1, 1, 32, 1, 1);
    runTest(false);
}

TEST_F(ktxTexture1WriteTestRGB8, Write1DArray) {
    helper.resize(createFlagBits::eArray, 4, 1, 1, 32, 1, 1);
    runTest(false);
}

TEST_F(ktxTexture1WriteTestRGBA8, Write1DArrayMipmap) {
    helper.resize(createFlagBits::eMipmapped | createFlagBits::eArray,
                  4, 1, 1, 32, 1, 1);
    runTest(false);
}

TEST_F(ktxTexture1WriteTestRGB8, Write2D) {
    helper.resize(createFlagBits::eNone, 1, 1, 2, 32, 32, 1);
    runTest(true);
}

TEST_F(ktxTexture1WriteTestRGB8, Write2DMipmap) {
    helper.resize(createFlagBits::eMipmapped, 1, 1, 2, 32, 32, 1);
    runTest(true);
}

TEST_F(ktxTexture1WriteTestRGBA8, Write2DArray) {
    helper.resize(createFlagBits::eArray, 4, 1, 2, 32, 32, 1);
    runTest(true);
}

TEST_F(ktxTexture1WriteTestRGBA8, Write2DArrayMipmap) {
    helper.resize(createFlagBits::eArray | createFlagBits::eMipmapped,
                  4, 1, 2, 32, 32, 1);
    runTest(true);
}

TEST_F(ktxTexture1WriteTestRGB8, 3D) {
    helper.resize(createFlagBits::eNone,1, 1, 3, 32, 32, 32);
    runTest(true);
}

TEST_F(ktxTexture1WriteTestRGB8, Write3DMipmap) {
    helper.resize(createFlagBits::eMipmapped, 1, 1, 3, 8, 8, 2);
    runTest(true);
}

TEST_F(ktxTexture1WriteTestRGB8, WriteCubemap) {
    helper.resize(createFlagBits::eNone, 1, 6, 2, 32, 32, 1);
    runTest(true);
}

TEST_F(ktxTexture1WriteTestRGBA8, WriteCubemapMipmap) {
    helper.resize(createFlagBits::eMipmapped,
                  1, 6, 2, 32, 32, 1);
    runTest(true);
}
TEST_F(ktxTexture1WriteTestRGBA8, WriteCubemapArrayMipmap) {
    helper.resize(createFlagBits::eMipmapped | createFlagBits::eArray,
                  4, 6, 2, 32, 32, 1);
    runTest(true);
}

TEST_F(ktxTexture1WriteTestRG16, Write2DMipmap) {
    helper.resize(createFlagBits::eMipmapped, 1, 1, 2, 32, 32, 1);
    runTest(true);
}

//----------------------------------------------------------
// Template for base fixture for ktxTexture_WriteKTX2 tests.
//----------------------------------------------------------

#include "vkformat_enum.h"
#define LIBKTX // To make dfd.h not include vulkan/vulkan_core.h.
#include "dfdutils/dfd.h"

template<typename component_type, ktx_uint32_t numComponents,
         GLenum internalformat>
class ktxTexture1WriteKTX2TestBase
      : public ktxTexture1WriteTestBase<component_type, numComponents, internalformat> {
  public:
    using createFlags = typename WriterTestHelper<component_type, numComponents, internalformat>::createFlags;
    using ktxTexture1WriteTestBase<component_type, numComponents, internalformat>::helper;

    ktxTexture1WriteKTX2TestBase() {
        requiredLevelAlignment = lcm4(sizeof(component_type) * numComponents);
    }

    void runTest(bool writeOrientationMeta, bool writeWriterMeta = true) {
        ktxTexture1* texture = 0;
        KTX_error_code result;
        ktx_uint8_t* ktxMemFile;
        ktx_size_t ktxMemFileLen;
        ktx_uint8_t* filePtr;
        ktxHashList* hl;
        ktxHashList_Create(&hl);
        ktx_uint8_t* kvData;
        ktx_uint32_t kvDataLen;

        result = ktxTexture1_Create(&helper.createInfo,
                                   KTX_TEXTURE_CREATE_ALLOC_STORAGE,
                                   &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                     << ktxErrorString(result);

        if (writeOrientationMeta) {
            // Reminder: this is for the KTX 1 texture we have just created.
            ktxHashList_AddKVPair(&texture->kvDataHead, KTX_ORIENTATION_KEY,
                                  (unsigned int)strlen(helper.orientation) + 1,
                                  helper.orientation);
            // This is for the comparison metadata.
            ktxHashList_AddKVPair(hl, KTX_ORIENTATION_KEY,
                                  (unsigned int)strlen(helper.orientation_ktx2) + 1,
                                  helper.orientation_ktx2);
        }
        // N.B. Writer metadata is not legal in a KTX v1 file but we know
        // we're going to write this out as a v2 file so okay.
        if (writeWriterMeta) {
            ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_KEY,
                                  (uint32_t)helper.writer_ktx2.size(),
                                  helper.writer_ktx2.data());
            ktxHashList_AddKVPair(hl, KTX_WRITER_KEY,
                                  (uint32_t)helper.writer_ktx2.size(),
                                  helper.writer_ktx2.data());
        }
        // Now update the comparison metadata by doing the things WriteKTX2 is
        // supposed to do so we can check it's actually doing it..
        ktxHashListEntry* pWriter = nullptr;;
        ktxHashList_FindEntry(hl, KTX_WRITER_KEY, &pWriter);
        result = appendLibId(hl, pWriter);
        EXPECT_EQ(result, KTX_SUCCESS);
        ktxHashList_Sort(hl);
        // And retrieve the comparison metadata.
        ktxHashList_Serialize(hl, &kvDataLen, &kvData);

        result = helper.copyImagesToTexture(texture);
        EXPECT_EQ(result, KTX_SUCCESS);

        EXPECT_EQ(helper.compareTexture1Images(texture->pData), true);

        result = ktxTexture1_WriteKTX2ToMemory(texture,
                                               &ktxMemFile,
                                               &ktxMemFileLen);

        ASSERT_TRUE(result == KTX_SUCCESS) << "ktxTexture_WriteKTX2ToMemory failed: "
                                           << ktxErrorString(result);

        KTX_header2* header = (KTX_header2*)ktxMemFile;

        EXPECT_EQ(memcmp(header, ktxId2, sizeof(ktxId2)), 0);
        EXPECT_EQ(helper.texinfo.compare(header), true);

        // Check the format descriptor.
        // This uses the same code to generate the comparator DFD as the
        // code under test. However we have separate tests for the
        // generator, so can be reasonably confident in it. This test
        // ensures there is a DFD in the file.
        ktx_uint32_t* dfd = vk2dfd(static_cast<VkFormat>(header->vkFormat));
        EXPECT_EQ(memcmp(ktxMemFile + header->dataFormatDescriptor.byteOffset,
                         dfd,
                         *dfd), 0);

        // Check the metadata.
        filePtr = ktxMemFile + header->keyValueData.byteOffset;
        EXPECT_EQ(header->keyValueData.byteLength, kvDataLen);
        EXPECT_EQ(memcmp(filePtr, kvData, kvDataLen), 0);
        filePtr += kvDataLen;

#if 0
        if (writeOrientationMeta) {
            EXPECT_EQ(header->keyValueData.byteLength,
                      helper.kvDataLenAll_ktx2);
            EXPECT_EQ(memcmp(filePtr, helper.kvDataAll_ktx2,
                             helper.kvDataLenAll_ktx2), 0);
            filePtr += helper.kvDataLenAll_ktx2;
        } else {
            EXPECT_EQ(header->keyValueData.byteLength,
                      helper.kvDataLenWriter_ktx2);
            EXPECT_EQ(memcmp(filePtr, helper.kvDataWriter_ktx2,
                             helper.kvDataLenWriter_ktx2), 0);
            filePtr += helper.kvDataLenWriter_ktx2;
        }
#endif
        // Offset of level 0 is first item in leveIndex after header.
        ktxLevelIndexEntry* levelIndex =
            reinterpret_cast<ktxLevelIndexEntry*>(ktxMemFile + sizeof(*header));

        ktx_uint64_t prevOffset = UINT64_MAX;
        for (ktx_uint32_t level = 0; level < helper.numLevels; level++) {
            ktx_uint64_t levelOffset = levelIndex[level].byteOffset;
            // Check offset is properly aligned.
            EXPECT_EQ(levelOffset % requiredLevelAlignment, 0U);
            // Check mipmaps are in order of increasing size in the file
            // therefore each offset should be smaller than the previous.
            EXPECT_LE(levelOffset, prevOffset);
            prevOffset = levelOffset;
        }

        EXPECT_EQ(helper.compareRawImages(levelIndex, ktxMemFile), true);
        delete ktxMemFile;
        ktxTexture_Destroy(ktxTexture(texture));
    }

    // Test rejection of unrecognized keys and passing of proprietary keys.
    void runTest(const char* unrecognizedKey, const char* proprietaryKey) {
        ktxTexture1* texture = 0;
        KTX_error_code result;
        ktx_uint8_t* ktxMemFile;
        ktx_size_t ktxMemFileLen;
        ktx_uint8_t* filePtr;
        ktx_uint8_t* kvData;
        ktx_uint32_t kvDataLen;

        result = ktxTexture1_Create(&helper.createInfo,
                                   KTX_TEXTURE_CREATE_ALLOC_STORAGE,
                                   &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                     << ktxErrorString(result);

        ktxHashList* hl;
        ktxHashList_Create(&hl);
        ktxHashList* hlists[2] = {&texture->kvDataHead, hl};
        // Add desired keys & values to both the texture and a comparator.
        char rubbishValue[] = "some rubbish value";
        for (uint32_t i = 0; i < 2; i++) {
            // N.B. Writer metadata is not legal in a KTX v1 file but we know
            // we're going to write this out as a v2 file so okay.
            ktxHashList_AddKVPair(hlists[i], KTX_WRITER_KEY,
                                  (uint32_t)helper.writer_ktx2.size(),
                                  helper.writer_ktx2.data());
            if (unrecognizedKey) {
                ktxHashList_AddKVPair(hlists[i], unrecognizedKey,
                                      sizeof(rubbishValue),
                                      rubbishValue);
            }
            if (proprietaryKey) {
                ktxHashList_AddKVPair(hlists[i], proprietaryKey,
                                      sizeof(rubbishValue),
                                      rubbishValue);
            }
            ktxHashList_Sort(hlists[i]);
        }
        // Get the library to add its Id to the writer key so it will be
        // included in the serialized data.
        ktxHashListEntry* pWriter;
        ktxHashList_FindEntry(hl, KTX_WRITER_KEY, &pWriter);
        appendLibId(hl, pWriter);
        ktxHashList_Sort(hl);
        ktxHashList_Serialize(hl, &kvDataLen, &kvData);
        ktxHashList_Destruct(hl);

        result = helper.copyImagesToTexture(texture);
        EXPECT_EQ(result, KTX_SUCCESS);

        EXPECT_EQ(helper.compareTexture1Images(texture->pData), true);

        result = ktxTexture1_WriteKTX2ToMemory(texture,
                                               &ktxMemFile,
                                               &ktxMemFileLen);

        if (unrecognizedKey == NULL) {
            ASSERT_TRUE(result == KTX_SUCCESS) << "ktxTexture_WriteKTX2ToMemory failed: "
                                               << ktxErrorString(result);

            KTX_header2* header = (KTX_header2*)ktxMemFile;

            EXPECT_EQ(memcmp(header, ktxId2, sizeof(ktxId2)), 0);
            EXPECT_EQ(helper.texinfo.compare(header), true);

            // Check the format descriptor.
            ktx_uint32_t* dfd = vk2dfd(static_cast<VkFormat>(header->vkFormat));
            EXPECT_EQ(memcmp(ktxMemFile + header->dataFormatDescriptor.byteOffset,
                             dfd,
                             *dfd), 0);

            // Check the metadata.
            filePtr = ktxMemFile + header->keyValueData.byteOffset;
            EXPECT_EQ(header->keyValueData.byteLength, kvDataLen);
            EXPECT_EQ(memcmp(filePtr, kvData, kvDataLen), 0);
            filePtr += helper.kvDataLen;

            // Offset of level 0 is first item in leveIndex after header.
            ktxLevelIndexEntry* levelIndex =
                reinterpret_cast<ktxLevelIndexEntry*>(ktxMemFile + sizeof(*header));

            ktx_uint64_t offset = UINT64_MAX;
            for (ktx_uint32_t level = 0; level < helper.numLevels; level++) {
                ktx_uint64_t levelOffset = levelIndex[level].byteOffset;
                // Check offset is properly aligned.
                EXPECT_EQ(levelOffset % requiredLevelAlignment, 0U);
                // Check mipmaps are in order of increasing size in the file
                // therefore each offset should be smaller than the previous.
                EXPECT_LE(levelOffset, offset);
                offset = levelOffset;
            }

            EXPECT_EQ(helper.compareRawImages(levelIndex, ktxMemFile), true);

            delete ktxMemFile;
        } else {
            EXPECT_EQ(result, KTX_INVALID_OPERATION);
        }

        ktxTexture_Destroy(ktxTexture(texture));
        delete kvData;

    }
  protected:
    ktx_uint32_t requiredLevelAlignment;
};

class ktxTexture1WriteKTX2TestRGBA8: public ktxTexture1WriteKTX2TestBase<GLubyte, 4, GL_RGBA8> { };
class ktxTexture1WriteKTX2TestRGB8: public ktxTexture1WriteKTX2TestBase<GLubyte, 3, GL_RGB8> { };
class ktxTexture1WriteKTX2TestRG16: public ktxTexture1WriteKTX2TestBase<GLushort, 2, GL_RG16> { };

/////////////////////////////////////////
// ktxTexture_WriteKTX2 tests
////////////////////////////////////////

TEST_F(ktxTexture1WriteKTX2TestRGBA8, Write1DNoOrientationMetadata) {
    helper.resize(createFlagBits::eNone, 1, 1, 1, 32, 1, 1);
    runTest(false);
}

TEST_F(ktxTexture1WriteKTX2TestRGBA8, Write1DNoWriterMetadata) {
    helper.resize(createFlagBits::eNone, 1, 1, 1, 32, 1, 1);
    runTest(false, false);
}

TEST_F(ktxTexture1WriteKTX2TestRGBA8, Write1DMipmap) {
    helper.resize(createFlagBits::eMipmapped, 1, 1, 1, 32, 1, 1);
    runTest(false);
}


TEST_F(ktxTexture1WriteKTX2TestRGB8, Write1DArray) {
    helper.resize(createFlagBits::eArray, 4, 1, 1, 32, 1, 1);
    runTest(false);
}

TEST_F(ktxTexture1WriteKTX2TestRGBA8, Write1DArrayMipmap) {
    helper.resize(createFlagBits::eMipmapped | createFlagBits::eArray,
                  4, 1, 1, 32, 1, 1);
    runTest(false);
}

TEST_F(ktxTexture1WriteKTX2TestRGBA8, Write2DNoOrientationMetadata) {
    helper.resize(createFlagBits::eNone, 1, 1, 2, 32, 32, 1);
    runTest(false);
}

TEST_F(ktxTexture1WriteKTX2TestRGBA8, Write2DNoWriterMetadata) {
    helper.resize(createFlagBits::eNone, 1, 1, 2, 32, 32, 1);
    runTest(false, false);
}

TEST_F(ktxTexture1WriteKTX2TestRGB8, Write2DMipmap) {
    helper.resize(createFlagBits::eMipmapped, 1, 1, 2, 32, 32, 1);
    runTest(true);
}

TEST_F(ktxTexture1WriteKTX2TestRGB8, Write2DMipmapUnrecognizedMetadata1) {
    helper.resize(createFlagBits::eMipmapped, 1, 1, 2, 32, 32, 1);
    runTest("KTXOrientation", NULL);
}

TEST_F(ktxTexture1WriteKTX2TestRGB8, Write2DMipmapUnrecognizedMetadata2) {
    helper.resize(createFlagBits::eMipmapped, 1, 1, 2, 32, 32, 1);
    runTest("ktxOrientation", NULL);
}

TEST_F(ktxTexture1WriteKTX2TestRGB8, Write2DMipmapProprietaryMetadata) {
    helper.resize(createFlagBits::eMipmapped, 1, 1, 2, 32, 32, 1);
    runTest(NULL, "MyProprietaryKey");
}

TEST_F(ktxTexture1WriteKTX2TestRGB8, Write2DMipmapUnrecogAndPropMetadata) {
    helper.resize(createFlagBits::eMipmapped, 1, 1, 2, 32, 32, 1);
    runTest("KTXOrientation", "MyProprietaryKey");
}
TEST_F(ktxTexture1WriteKTX2TestRGBA8, Write2DArray) {
    helper.resize(createFlagBits::eArray, 4, 1, 2, 32, 32, 1);
    runTest(true);
}

TEST_F(ktxTexture1WriteKTX2TestRGBA8, Write2DArrayMipmap) {
    helper.resize(createFlagBits::eArray | createFlagBits::eMipmapped,
                  4, 1, 2, 32, 32, 1);
    runTest(true);
}

TEST_F(ktxTexture1WriteKTX2TestRGB8, 3D) {
    helper.resize(createFlagBits::eNone,1, 1, 3, 32, 32, 32);
    runTest(true);
}

TEST_F(ktxTexture1WriteKTX2TestRGB8, Write3DMipmap) {
    helper.resize(createFlagBits::eMipmapped, 1, 1, 3, 8, 8, 2);
    runTest(true);
}

TEST_F(ktxTexture1WriteKTX2TestRGB8, WriteCubemap) {
    helper.resize(createFlagBits::eNone, 1, 6, 2, 32, 32, 1);
    runTest(true);
}

TEST_F(ktxTexture1WriteKTX2TestRGBA8, WriteCubemapMipmap) {
    helper.resize(createFlagBits::eMipmapped,
                  1, 6, 2, 32, 32, 1);
    runTest(true);
}

TEST_F(ktxTexture1WriteKTX2TestRGBA8, WriteCubemapArrayMipmap) {
    helper.resize(createFlagBits::eMipmapped | createFlagBits::eArray,
                  4, 6, 2, 32, 32, 1);
    runTest(true);
}

TEST_F(ktxTexture1WriteKTX2TestRG16, Write2DMipmap) {
    helper.resize(createFlagBits::eMipmapped, 1, 1, 2, 32, 32, 1);
    runTest(true);
}

//----------------------------------------------------------
// Template for base fixture for ktxTexture2_Read tests.
//----------------------------------------------------------

template<typename component_type, ktx_uint32_t numComponents,
         GLenum internalformat>
class ktxTexture2ReadTestBase
      : public ktxTexture1WriteTestBase<component_type, numComponents, internalformat> {
  public:
    using createFlags = typename WriterTestHelper<component_type, numComponents, internalformat>::createFlags;
    using ktxTexture1WriteTestBase<component_type, numComponents, internalformat>::helper;

    ktxTexture2ReadTestBase() { }

    ~ktxTexture2ReadTestBase() {
        if (ktx2MemFile) delete ktx2MemFile;
    }

    void resize(createFlags flags,
                ktx_uint32_t numLayers, ktx_uint32_t numFaces,
                ktx_uint32_t numDimensions,
                ktx_uint32_t width, ktx_uint32_t height, ktx_uint32_t depth)
    {
        ktxTexture1* texture = 0;
        KTX_error_code result;

        helper.resize(flags, numLayers, numFaces, numDimensions,
                      width, height, depth);

        result = ktxTexture1_Create(&helper.createInfo,
                                    KTX_TEXTURE_CREATE_ALLOC_STORAGE,
                                    &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture1_Create failed: "
                                     << ktxErrorString(result);

        // Reminder: this is for the KTX 1 texture we have just created.
        ktxHashList_AddKVPair(&texture->kvDataHead, KTX_ORIENTATION_KEY,
                              (unsigned int)strlen(helper.orientation) + 1,
                              helper.orientation);
        ktxHashList_AddKVPair(&texture->kvDataHead, KTX_WRITER_KEY,
                              (uint32_t)helper.writer_ktx2.size(),
                              helper.writer_ktx2.data());

        result = helper.copyImagesToTexture(texture);
        EXPECT_EQ(result, KTX_SUCCESS);

        EXPECT_EQ(helper.compareTexture1Images(texture->pData), true);

        result = ktxTexture1_WriteKTX2ToMemory(texture,
                                               &ktx2MemFile,
                                               &ktx2MemFileLen);
        ASSERT_TRUE(result == KTX_SUCCESS) << "ktxTexture1_WriteKTX2ToMemory failed: "
                                           << ktxErrorString(result);
        fileHeader = (KTX_header2*)ktx2MemFile;
        levelIndex = (ktxLevelIndexEntry*)(ktx2MemFile + sizeof(KTX_header2));

        ktxTexture1_destruct(texture);
    }

    void runTest() {
        ktxTexture2* texture2 = 0;
        KTX_error_code result;

        result = ktxTexture2_CreateFromMemory(ktx2MemFile, ktx2MemFileLen,
                                        KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                        &texture2);

        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(texture2 != NULL) << "ktxTexture2_Create failed: "
                                      << ktxErrorString(result);

        EXPECT_EQ(texture2->classId, ktxTexture2_c);
        EXPECT_EQ(helper.texinfo.compare(texture2), true);
        EXPECT_NE(texture2->kvDataHead, (ktxKVListEntry*)0);

        // Check the levelOffsets are as expected.
        ktx_size_t baseOffset = levelIndex[helper.numLevels - 1].byteOffset;
        for (ktx_uint32_t level = 0; level < texture2->numLevels; level++) {
            ktx_size_t levelOffset;
            result = ktxTexture2_GetImageOffset(texture2, level, 0, 0,
                                                &levelOffset);
            EXPECT_EQ(result, KTX_SUCCESS);
            EXPECT_EQ(levelOffset, levelIndex[level].byteOffset - baseOffset);
        }

        ktxTexture2_destruct(texture2);

    }

    protected:
        ktx_uint8_t* ktx2MemFile;
        ktx_size_t ktx2MemFileLen;
        KTX_header2* fileHeader;
        ktxLevelIndexEntry* levelIndex;
};

class ktxTexture2ReadTestRGBA8: public ktxTexture2ReadTestBase<GLubyte, 4, GL_RGBA8> { };

/////////////////////////////////////////
// ktxTexture_WriteKTX2 tests
////////////////////////////////////////

TEST_F(ktxTexture2ReadTestRGBA8, Read1D) {
    resize(createFlagBits::eNone, 1, 1, 1, 32, 1, 1);
    runTest();
}

TEST_F(ktxTexture2ReadTestRGBA8, Read2D) {
    resize(createFlagBits::eNone, 1, 1, 2, 32, 32, 1);
    runTest();
}

TEST_F(ktxTexture2ReadTestRGBA8, Read3D) {
    resize(createFlagBits::eNone, 1, 1, 3, 32, 32, 32);
    runTest();
}

TEST_F(ktxTexture2ReadTestRGBA8, Read1DMipmap) {
    resize(createFlagBits::eMipmapped, 1, 1, 1, 64, 1, 1);
    runTest();
}

TEST_F(ktxTexture2ReadTestRGBA8, Read2DMipmap) {
    resize(createFlagBits::eMipmapped, 1, 1, 2, 64, 64, 1);
    runTest();
}

TEST_F(ktxTexture2ReadTestRGBA8, Read3DMipmap) {
    resize(createFlagBits::eMipmapped, 1, 1, 3, 64, 64, 32);
    runTest();
}

class ktxTexture2_BasisCompressTest : public ktxTexture2TestBase<GLubyte, 4, GL_RGBA8>  { };

/////////////////////////////////////////
// ktxTexture2_BasisCompress tests
////////////////////////////////////////

TEST_F(ktxTexture2_BasisCompressTest, Compress) {
    ktxTexture2* texture;
    ktx_uint64_t dataSize;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        dataSize = texture->dataSize;
        result = ktxTexture2_CompressBasis(texture, 0);
        EXPECT_EQ(result, KTX_SUCCESS);
        EXPECT_EQ(texture->supercompressionScheme, KTX_SS_BASIS_LZ);
        EXPECT_TRUE(texture->_private->_supercompressionGlobalData > (ktx_uint8_t*)0);
        EXPECT_EQ(texture->numLevels, helper.numLevels);
        EXPECT_LT(texture->dataSize, dataSize);
        // How else to test the result?

        result = ktxTexture2_TranscodeBasis(texture, KTX_TTF_BC1_RGB, 0);
        EXPECT_EQ(result, KTX_SUCCESS);
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

class ktxTexture2_GetNumComponentsTestR8 : public ktxTexture2TestBase<GLubyte, 1, GL_R8> { };
class ktxTexture2_GetNumComponentsTestRG8 : public ktxTexture2TestBase<GLubyte, 2, GL_RG8> { };
class ktxTexture2_GetNumComponentsTestRGB8 : public ktxTexture2TestBase<GLubyte, 3, GL_RGB8> { };
class ktxTexture2_GetNumComponentsTestRGBA8 : public ktxTexture2TestBase<GLubyte, 4, GL_RGBA8> { };
class ktxTexture2_MetadataTest : public ktxTexture2TestBase<GLubyte, 4, GL_RGBA8> { };

////////////////////////////////////////////
// ktxTexture2_GetNumComponents tests
///////////////////////////////////////////

TEST_F(ktxTexture2_GetNumComponentsTestR8, Uncompressed) {
    ktxTexture2* texture;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t components = ktxTexture2_GetNumComponents(texture);
        EXPECT_EQ(components, 1U);
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

TEST_F(ktxTexture2_GetNumComponentsTestR8, BasisLZ) {
    ktxTexture2* texture;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t components = ktxTexture2_GetNumComponents(texture);
        EXPECT_EQ(components, 1U);
        ktxTexture2_CompressBasis(texture, 0);
        EXPECT_EQ(components, ktxTexture2_GetNumComponents(texture));
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

TEST_F(ktxTexture2_GetNumComponentsTestR8, UASTC) {
    ktxTexture2* texture;
    KTX_error_code result;
    ktxBasisParams cparams = { };

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t components = ktxTexture2_GetNumComponents(texture);
        EXPECT_EQ(components, 1U);
        cparams.uastc = KTX_TRUE;
        ktxTexture2_CompressBasisEx(texture, &cparams);
        EXPECT_EQ(components, ktxTexture2_GetNumComponents(texture));
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

TEST_F(ktxTexture2_GetNumComponentsTestRG8, Uncompressed) {
    ktxTexture2* texture;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t components = ktxTexture2_GetNumComponents(texture);
        EXPECT_EQ(components, 2U);
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

TEST_F(ktxTexture2_GetNumComponentsTestRG8, BasisLZ) {
    ktxTexture2* texture;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t components = ktxTexture2_GetNumComponents(texture);
        EXPECT_EQ(components, 2U);
        ktxTexture2_CompressBasis(texture, 0);
        EXPECT_EQ(components, ktxTexture2_GetNumComponents(texture));
    }
}

TEST_F(ktxTexture2_GetNumComponentsTestRG8, UASTC) {
    ktxTexture2* texture;
    KTX_error_code result;
    ktxBasisParams cparams = { };

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t components = ktxTexture2_GetNumComponents(texture);
        EXPECT_EQ(components, 2U);
        cparams.uastc = KTX_TRUE;
        ktxTexture2_CompressBasisEx(texture, &cparams);
        EXPECT_EQ(components, ktxTexture2_GetNumComponents(texture));
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

TEST_F(ktxTexture2_GetNumComponentsTestRGB8, Uncompressed) {
    ktxTexture2* texture;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t components = ktxTexture2_GetNumComponents(texture);
        EXPECT_EQ(components, 3U);
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

TEST_F(ktxTexture2_GetNumComponentsTestRGB8, BasisLZ) {
    ktxTexture2* texture;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t components = ktxTexture2_GetNumComponents(texture);
        EXPECT_EQ(components, 3U);
        ktxTexture2_CompressBasis(texture, 0);
        EXPECT_EQ(components, ktxTexture2_GetNumComponents(texture));
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

TEST_F(ktxTexture2_GetNumComponentsTestRGB8, UASTC) {
    ktxTexture2* texture;
    KTX_error_code result;
    ktxBasisParams cparams = { };

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t components = ktxTexture2_GetNumComponents(texture);
        EXPECT_EQ(components, 3U);
        cparams.uastc = KTX_TRUE;
        ktxTexture2_CompressBasisEx(texture, &cparams);
        EXPECT_EQ(components, ktxTexture2_GetNumComponents(texture));
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

TEST_F(ktxTexture2_GetNumComponentsTestRGBA8, Uncompressed) {
    ktxTexture2* texture;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t components = ktxTexture2_GetNumComponents(texture);
        EXPECT_EQ(components, 4U);
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

TEST_F(ktxTexture2_GetNumComponentsTestRGBA8, BasisLZ) {
    ktxTexture2* texture;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t components = ktxTexture2_GetNumComponents(texture);
        EXPECT_EQ(components, 4U);
        ktxTexture2_CompressBasis(texture, 0);
        EXPECT_EQ(components, ktxTexture2_GetNumComponents(texture));
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

TEST_F(ktxTexture2_GetNumComponentsTestRGBA8, UASTC) {
    ktxTexture2* texture;
    KTX_error_code result;
    ktxBasisParams cparams = { };

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t components = ktxTexture2_GetNumComponents(texture);
        EXPECT_EQ(components, 4U);
        cparams.uastc = KTX_TRUE;
        ktxTexture2_CompressBasisEx(texture, &cparams);
        EXPECT_EQ(components, ktxTexture2_GetNumComponents(texture));
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

TEST_F(ktxTexture2_MetadataTest, EmptyValue) {
    ktxTexture2* texture;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        result = ktxHashList_AddKVPair(&texture->kvDataHead,
                                       "MSCtestKey", 0, nullptr);
        EXPECT_EQ(result, KTX_SUCCESS);

        ktx_size_t newMemFileLen;
        ktx_uint8_t* newMemFile;
        result = ktxTexture_WriteToMemory(ktxTexture(texture), &newMemFile,
                                          &newMemFileLen);
        EXPECT_EQ(result, KTX_SUCCESS);
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));

        result = ktxTexture2_CreateFromMemory(newMemFile, newMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t valueLen;
        ktx_uint8_t* value;
        result = ktxHashList_FindValue(&texture->kvDataHead,
                                      "MSCtestKey", &valueLen, (void**)&value);
        EXPECT_EQ(result, KTX_SUCCESS);
        EXPECT_EQ(valueLen, 0U);
        EXPECT_EQ(value, nullptr);

        if (newMemFile)
            free(newMemFile);
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}

#if defined(TestNoMetadata)
TEST_F(ktxTexture2_MetadataTest, NoMetadata) {
    ktxTexture2* texture;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktxHashList_Destruct(&texture->kvDataHead);
        ktxTexture(texture)->kvDataHead = nullptr;
        ktxTexture(texture)->kvDataLen = 0;


        ktx_size_t newMemFileLen;
        ktx_uint8_t* newMemFile;
        ::__disableWriterMetadata__ = KTX_TRUE;
        result = ktxTexture_WriteToMemory(ktxTexture(texture), &newMemFile,
                                          &newMemFileLen);
        ::__disableWriterMetadata__ = KTX_FALSE;
        EXPECT_EQ(result, KTX_SUCCESS);
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));

        result = ktxTexture2_CreateFromMemory(newMemFile, newMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t valueLen;
        ktx_uint8_t* value;
        EXPECT_EQ(result, KTX_SUCCESS);
        EXPECT_EQ(texture->kvDataLen, 0U);
        EXPECT_EQ(texture->kvDataHead, nullptr);

        if (newMemFile)
            free(newMemFile);
        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));
    }
}
#endif

TEST_F(ktxTexture2_MetadataTest, NoLibVersionDupOnMultipleWrites) {
    ktxTexture2* texture;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        const ktx_uint32_t iterations = 2;
        ktx_size_t newMemFileLens[iterations];
        ktx_uint8_t* newMemFiles[iterations];
        for (uint32_t i = 0; i < iterations; i++) {
            result = ktxTexture_WriteToMemory(ktxTexture(texture),
                                              &newMemFiles[i],
                                              &newMemFileLens[i]);
            EXPECT_EQ(result, KTX_SUCCESS);
        }
        for (uint32_t i = 1; i < iterations; i++) {
            EXPECT_EQ(newMemFileLens[i-1], newMemFileLens[i]);
        }

        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));

        std::string writers[iterations];
        for (uint32_t i = 0; i < iterations; i++) {
            ktx_uint32_t valueLen;
            ktx_uint8_t* value;
            result = ktxTexture2_CreateFromMemory(newMemFiles[i],
                                                  newMemFileLens[i],
                                                  KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                                  &texture);
            ASSERT_TRUE(result == KTX_SUCCESS);
            ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                         << ktxErrorString(result);
            ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

            result = ktxHashList_FindValue(&texture->kvDataHead,
                                          "KTXwriter",
                                          &valueLen, (void**)&value);
            EXPECT_EQ(result, KTX_SUCCESS);
            // We want ktxWriteTo* to NUL terminate the value when adding
            // the libktx version.
            ASSERT_TRUE(value[valueLen-1] == '\0')
                        << "KTXwriter not NUL terminated";
            writers[i] = (char*)value;
            if (texture)
                ktxTexture_Destroy(ktxTexture(texture));
        }

        for (uint32_t i = 1; i < iterations; i++) {
            // This is a valid test because we know all our calls to libktx
            // use the same version of libktx.
            EXPECT_EQ(0, writers[i-1].compare(writers[i]));
        }

        for (uint32_t i = 0; i < iterations; i++) {
            if (newMemFiles[i])
                free(newMemFiles[i]);
        }
    }
}

TEST_F(ktxTexture2_MetadataTest, LibVersionUpdatedCorrectly) {
    ktxTexture2* texture;
    KTX_error_code result;

    if (ktxMemFile != NULL) {
        result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        ktx_uint32_t curWriterLen;
        ktx_uint8_t* curWriterVal;
        result = ktxHashList_FindValue(&texture->kvDataHead,
                                       "KTXwriter",
                                        &curWriterLen, (void**)&curWriterVal);
        EXPECT_EQ(result, KTX_SUCCESS);
        // We want ktxWriteTo* to NUL terminate the value when adding
        // the libktx version.
        ASSERT_TRUE(curWriterVal[curWriterLen-1] == '\0')
                    << "KTXwriter not NUL terminated";
        // The pointer returned by FindValue becomes invalid when the texture
        // is destroyed hence saving to this string. -1 to omit the terminator.
        std::string curWriter((char*)curWriterVal, curWriterLen-1);
        std::string writer(curWriter);
        size_t slash_pos = writer.find_last_of('/');
        ASSERT_TRUE(slash_pos != std::string::npos)
                    << "KTXwriter does not have lib version.";
        writer.replace(slash_pos + 2, std::string::npos, "libktx v3.0.0");
        result = ktxHashList_AddKVPair(&texture->kvDataHead,
                                       "KTXwriter",
                                       (ktx_uint32_t)writer.length(),
                                       writer.c_str());
        EXPECT_EQ(result, KTX_SUCCESS);


        ktx_size_t newMemFileLen;
        ktx_uint8_t* newMemFile;
        result = ktxTexture_WriteToMemory(ktxTexture(texture),
                                          &newMemFile,
                                          &newMemFileLen);
        EXPECT_EQ(result, KTX_SUCCESS);

        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));

        ktx_uint32_t newWriterLen;
        ktx_uint8_t* newWriterVal;
        result = ktxTexture2_CreateFromMemory(newMemFile,
                                              newMemFileLen,
                                              KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                              &texture);
        ASSERT_TRUE(result == KTX_SUCCESS);
        ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                     << ktxErrorString(result);
        ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

        result = ktxHashList_FindValue(&texture->kvDataHead,
                                       "KTXwriter",
                                        &newWriterLen, (void**)&newWriterVal);
        EXPECT_EQ(result, KTX_SUCCESS);
        ASSERT_TRUE(newWriterVal[newWriterLen-1] == '\0')
                    << "KTXwriter not NUL terminated";

        EXPECT_EQ(0, curWriter.compare((char *)newWriterVal));

        if (texture)
            ktxTexture_Destroy(ktxTexture(texture));

        if (newMemFile)
            free(newMemFile);
    }
}

////////////////////////////////////////////
// Unicode file name tests
///////////////////////////////////////////

fs::path imagePath;

TEST(UnicodeFileNames, CreateFrom) {
    std::vector<std::string> fileSet = {
        u8"hűtő.ktx",
        u8"hűtő.ktx2",
        u8"نَسِيج.ktx",
        u8"نَسِيج.ktx2",
        u8"テクスチャ.ktx",
        u8"テクスチャ.ktx2",
        u8"质地.ktx",
        u8"质地.ktx2",
        u8"조직.ktx",
        u8"조직.ktx2"
    };

    std::vector<std::string>::const_iterator it;


    fs::path filePath = imagePath;
    for (it = fileSet.begin(); it < fileSet.end(); it++) {
        ktx_error_code_e result;
        ktxTexture* texture = nullptr;

        filePath.replace_filename(fs::u8path(*it));

        result = ktxTexture_CreateFromNamedFile(
            filePath.u8string().c_str(),
            KTX_TEXTURE_CREATE_NO_FLAGS,
            &texture);
        EXPECT_EQ(result, KTX_SUCCESS);
        EXPECT_NE(texture, (ktxTexture*)0);
        if (texture) {
            ktxTexture_Destroy(texture);
            texture = nullptr;
        }

        if (filePath.extension() == ".ktx") {
            result = ktxTexture1_CreateFromNamedFile(
                filePath.u8string().c_str(),
                KTX_TEXTURE_CREATE_NO_FLAGS,
                (ktxTexture1**)&texture);
        } else {
            result = ktxTexture2_CreateFromNamedFile(
                filePath.u8string().c_str(),
                KTX_TEXTURE_CREATE_NO_FLAGS,
                (ktxTexture2**)&texture);
        }
        EXPECT_EQ(result, KTX_SUCCESS);
        EXPECT_NE(texture, (ktxTexture*)0);
        if (texture) ktxTexture_Destroy(texture);
    }
}

// The ASTC encoder and decoder are heavily tested elsewhere hence the focus
// of these tests is the mechanics of encoding and decoding a ktxTexture2
// and the resulting changes to the ktxTexture2 object.

//------------------------------------------------------------
// Template for base fixture for ASTC encode and decode tests.
//------------------------------------------------------------

fs::path ktxdiffPath;

template<typename component_type, ktx_uint32_t numComponents,
         GLenum internalformat>
class ktxTexture2AstcLdrEncodeDecodeTestBase
      : public ktxTexture2TestBase<component_type, numComponents, internalformat> {

  protected:
    using ktxTextureTestBase<component_type, numComponents, internalformat>::helper;
    using ktxTextureTestBase<component_type, numComponents, internalformat>::ktxMemFile;
    using ktxTextureTestBase<component_type, numComponents, internalformat>::ktxMemFileLen;

  public:
    void runTest(ktx_pack_astc_block_dimension_e blockDimension) {
        ktxTexture2* texture;
        KTX_error_code result;
        auto tmpDir = fs::temp_directory_path();

        fs::path original = tmpDir / "CompressToAstcLdrThenDecode_original.ktx2";
        fs::path decoded = tmpDir / "CompressToAstcLdrThenDecode_decoded.ktx2";
        fs::path ktxdiffOut = tmpDir / "ktxdiffOut.txt";

        if (ktxMemFile != NULL) {
            result = ktxTexture2_CreateFromMemory(ktxMemFile, ktxMemFileLen,
                                                  KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT,
                                                  &texture);
            ASSERT_TRUE(result == KTX_SUCCESS);
            ASSERT_TRUE(texture != NULL) << "ktxTexture_CreateFromMemory failed: "
                                         << ktxErrorString(result);
            ASSERT_TRUE(texture->pData != NULL) << "Image data not loaded";

            result = ktxTexture2_WriteToNamedFile(texture, original.u8string().c_str());
            ASSERT_TRUE(result == KTX_SUCCESS);

            auto depth = texture->baseDepth;
            auto height = texture->baseHeight;
            auto width = texture->baseWidth;
            //ktx_uint64_t dataSize = texture->dataSize;
            auto dataSize = texture->dataSize;

            ASSERT_TRUE(depth == 1);

            ktxAstcParams params;
            params.structSize = sizeof(params);
            params.threadCount = 1;
            params.blockDimension = blockDimension;
            params.mode = KTX_PACK_ASTC_ENCODER_MODE_DEFAULT;
            params.qualityLevel = KTX_PACK_ASTC_QUALITY_LEVEL_FAST;
            params.normalMap = false;
            params.perceptual = false;
            params.inputSwizzle[0] = 'R';
            params.inputSwizzle[1] = 'G';
            params.inputSwizzle[2] = 'B';
            params.inputSwizzle[3] = 'A';
            result = ktxTexture2_CompressAstcEx(texture, &params);

            EXPECT_EQ(result, KTX_SUCCESS);
            VkFormat expectedFormat = blockDimensionToFormat(blockDimension);
            // Oops! Maybe it was a mistake to define texture.vkFormat as unsigned.
            EXPECT_TRUE(texture->vkFormat == (ktx_uint32_t)expectedFormat);

            uint32_t* pBdb = texture->pDfd+1;
            if (isFormatFloat()) {
                EXPECT_TRUE(KHR_DFDSVAL(pBdb, 1, QUALIFIERS) & KHR_DF_SAMPLE_DATATYPE_FLOAT);
                EXPECT_TRUE(KHR_DFDSVAL(pBdb, 1, QUALIFIERS) & KHR_DF_SAMPLE_DATATYPE_SIGNED);
            } else if (isFormatSrgb()) {
                EXPECT_TRUE(KHR_DFDVAL(pBdb, TRANSFER) == KHR_DF_TRANSFER_SRGB);
            }
            khr_df_model_e model = static_cast<khr_df_model_e>(KHR_DFDVAL(pBdb, MODEL));
            EXPECT_EQ(model, KHR_DF_MODEL_ASTC);
            EXPECT_EQ(texture->supercompressionScheme, KTX_SS_NONE);
            EXPECT_TRUE(texture->_private->_supercompressionGlobalData == (ktx_uint8_t*)0);
            EXPECT_EQ(texture->numLevels, helper.numLevels);
            EXPECT_EQ(texture->baseDepth, depth);
            EXPECT_EQ(texture->baseHeight, height);
            EXPECT_EQ(texture->baseWidth, width);
            EXPECT_LT(texture->dataSize, dataSize);

            result = ktxTexture2_DecodeAstc(texture);
            EXPECT_EQ(result, KTX_SUCCESS);
            if (isFormatFloat())
                EXPECT_EQ(texture->vkFormat, (ktx_uint32_t)VK_FORMAT_R32G32B32A32_SFLOAT);
            else if (isFormatSrgb())
                EXPECT_EQ(texture->vkFormat, (ktx_uint32_t)VK_FORMAT_R8G8B8A8_SRGB);
            else
                EXPECT_EQ(texture->vkFormat, (ktx_uint32_t)VK_FORMAT_R8G8B8A8_UNORM);
            model = static_cast<khr_df_model_e>(KHR_DFDVAL(texture->pDfd+1, MODEL));
            EXPECT_EQ(model, KHR_DF_MODEL_RGBSDA);
            EXPECT_EQ(depth, texture->baseDepth);
            result = ktxTexture2_WriteToNamedFile(texture, decoded.u8string().c_str());
            int status;
            if constexpr (internalformat != (GLenum)GL_RGB8 && internalformat != (GLenum)GL_SRGB8) {
                std::string command = ktxdiffPath.u8string();
                command += " " + original.string() + " " + decoded.string() + " 0.01 > " + ktxdiffOut.string();
                status = std::system(command.c_str());
            } else {
                // ASTC formats always decode to a 4-component format as there is no way to
                // tell prior to decode if any blocks have an alpha channel. Since we don't
                // have a command comparing only some components of the image data, we can't
                // test for a data match.
                status = 0;
            }
            EXPECT_EQ(status, 0);
            EXPECT_EQ(texture->baseHeight, height);
            EXPECT_EQ(texture->baseWidth, width);
            if (status != 0) {
                std::cout << std::ifstream(ktxdiffOut).rdbuf();
            }
            if (texture) {
                ktxTexture_Destroy(ktxTexture(texture));
                fs::remove(original);
                fs::remove(decoded);
                fs::remove(ktxdiffOut);
            }
        }
    }
  protected:
    bool isFormatFloat() {
        // Test does not yet support float formats
        return false;
    }

    bool isFormatSrgb() {
        switch(internalformat) {
            case GL_SRGB8_ALPHA8: [[fallthrough]];
            case GL_SRGB8:
                return true;
            default:
                return false;
        }
    }

    VkFormat blockDimensionToFormat(ktx_pack_astc_block_dimension_e blockDimension) {
        if (isFormatSrgb())
            return blockDimensionToSrgbFormat(blockDimension);
        else
            return blockDimensionToUnormFormat(blockDimension);
    }

    VkFormat blockDimensionToSrgbFormat(ktx_pack_astc_block_dimension_e blockDimension) {
        switch(blockDimension) {
            case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4: return VK_FORMAT_ASTC_4x4_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4: return VK_FORMAT_ASTC_5x4_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5: return VK_FORMAT_ASTC_5x5_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5: return VK_FORMAT_ASTC_6x5_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6: return VK_FORMAT_ASTC_6x6_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_8x5: return VK_FORMAT_ASTC_8x5_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_8x6: return VK_FORMAT_ASTC_8x6_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_10x5: return VK_FORMAT_ASTC_10x5_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_10x6: return VK_FORMAT_ASTC_10x6_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_8x8:  return VK_FORMAT_ASTC_8x8_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_10x8: return VK_FORMAT_ASTC_10x8_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_10x10: return VK_FORMAT_ASTC_10x10_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_12x10: return VK_FORMAT_ASTC_12x10_SRGB_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_12x12: return VK_FORMAT_ASTC_12x12_SRGB_BLOCK;
            // 3D formats
            case KTX_PACK_ASTC_BLOCK_DIMENSION_3x3x3: return VK_FORMAT_ASTC_3x3x3_SRGB_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_4x3x3: return VK_FORMAT_ASTC_4x3x3_SRGB_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x3: return VK_FORMAT_ASTC_4x4x3_SRGB_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x4: return VK_FORMAT_ASTC_4x4x4_SRGB_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4x4: return VK_FORMAT_ASTC_5x4x4_SRGB_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x4: return VK_FORMAT_ASTC_5x5x4_SRGB_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x5: return VK_FORMAT_ASTC_5x5x5_SRGB_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5x5: return VK_FORMAT_ASTC_6x5x5_SRGB_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x5: return VK_FORMAT_ASTC_6x6x5_SRGB_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x6: return VK_FORMAT_ASTC_6x6x6_SRGB_BLOCK_EXT;
            default: return VK_FORMAT_UNDEFINED;
        };
    }

    VkFormat blockDimensionToUnormFormat(ktx_pack_astc_block_dimension_e blockDimension) {
        switch(blockDimension) {
            case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4: return VK_FORMAT_ASTC_4x4_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4: return VK_FORMAT_ASTC_5x4_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5: return VK_FORMAT_ASTC_5x5_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5: return VK_FORMAT_ASTC_6x5_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6: return VK_FORMAT_ASTC_6x6_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_8x5: return VK_FORMAT_ASTC_8x5_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_8x6: return VK_FORMAT_ASTC_8x6_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_10x5: return VK_FORMAT_ASTC_10x5_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_10x6: return VK_FORMAT_ASTC_10x6_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_8x8:  return VK_FORMAT_ASTC_8x8_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_10x8: return VK_FORMAT_ASTC_10x8_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_10x10: return VK_FORMAT_ASTC_10x10_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_12x10: return VK_FORMAT_ASTC_12x10_UNORM_BLOCK;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_12x12: return VK_FORMAT_ASTC_12x12_UNORM_BLOCK;
            // 3D formats
            case KTX_PACK_ASTC_BLOCK_DIMENSION_3x3x3: return VK_FORMAT_ASTC_3x3x3_UNORM_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_4x3x3: return VK_FORMAT_ASTC_4x3x3_UNORM_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x3: return VK_FORMAT_ASTC_4x4x3_UNORM_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_4x4x4: return VK_FORMAT_ASTC_4x4x4_UNORM_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_5x4x4: return VK_FORMAT_ASTC_5x4x4_UNORM_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x4: return VK_FORMAT_ASTC_5x5x4_UNORM_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_5x5x5: return VK_FORMAT_ASTC_5x5x5_UNORM_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_6x5x5: return VK_FORMAT_ASTC_6x5x5_UNORM_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x5: return VK_FORMAT_ASTC_6x6x5_UNORM_BLOCK_EXT;
            case KTX_PACK_ASTC_BLOCK_DIMENSION_6x6x6: return VK_FORMAT_ASTC_6x6x6_UNORM_BLOCK_EXT;
            default: return VK_FORMAT_UNDEFINED;
        };
    }
};

// Test fixtures for ASTC encode and decode tests.

class ktxTexture2_AstcLdrEncodeDecodeTestRGBA8_UNORM
    : public ktxTexture2AstcLdrEncodeDecodeTestBase<GLubyte, 4, GL_RGBA8> { };
class ktxTexture2_AstcLdrEncodeDecodeTestRGBA8_SRGB
    : public ktxTexture2AstcLdrEncodeDecodeTestBase<GLubyte, 4, GL_SRGB8_ALPHA8> { };
class ktxTexture2_AstcLdrEncodeDecodeTestRGB8_UNORM
    : public ktxTexture2AstcLdrEncodeDecodeTestBase<GLubyte, 4, GL_RGB8> { };
class ktxTexture2_AstcLdrEncodeDecodeTestRGB8_SRGB
    : public ktxTexture2AstcLdrEncodeDecodeTestBase<GLubyte, 4, GL_SRGB8> { };

////////////////////////////////////////////
// ASTC encode & decode tests
///////////////////////////////////////////

TEST_F(ktxTexture2_AstcLdrEncodeDecodeTestRGBA8_UNORM, CompressToAstc4x4LdrThenDecode) {
    runTest(KTX_PACK_ASTC_BLOCK_DIMENSION_4x4);
}

TEST_F(ktxTexture2_AstcLdrEncodeDecodeTestRGBA8_SRGB, CompressToAstc4x4LdrThenDecode) {
    runTest(KTX_PACK_ASTC_BLOCK_DIMENSION_4x4);
}

TEST_F(ktxTexture2_AstcLdrEncodeDecodeTestRGB8_UNORM, CompressToAstc4x4LdrThenDecode) {
    runTest(KTX_PACK_ASTC_BLOCK_DIMENSION_4x4);
}

TEST_F(ktxTexture2_AstcLdrEncodeDecodeTestRGB8_SRGB, CompressToAstc4x4LdrThenDecode) {
    runTest(KTX_PACK_ASTC_BLOCK_DIMENSION_4x4);
}

TEST_F(ktxTexture2_AstcLdrEncodeDecodeTestRGBA8_UNORM, CompressToAstc8x5LdrThenDecode) {
    runTest(KTX_PACK_ASTC_BLOCK_DIMENSION_8x5);
}

TEST_F(ktxTexture2_AstcLdrEncodeDecodeTestRGBA8_SRGB, CompressToAstc8x5LdrThenDecode) {
    runTest(KTX_PACK_ASTC_BLOCK_DIMENSION_8x5);
}
TEST_F(ktxTexture2_AstcLdrEncodeDecodeTestRGBA8_UNORM, CompressToAstc12x12LdrThenDecode) {
    runTest(KTX_PACK_ASTC_BLOCK_DIMENSION_12x12);
}

TEST_F(ktxTexture2_AstcLdrEncodeDecodeTestRGBA8_SRGB, CompressToAstc12x12LdrThenDecode) {
    runTest(KTX_PACK_ASTC_BLOCK_DIMENSION_12x12);
}

}  // namespace

#if defined(_WIN32)
// For Windows, we convert the UTF-8 path to a UTF-16 path to force using
// the APIs that correctly handle unicode characters.
inline std::wstring
DecodeUTF8Path(std::string path) {
    std::wstring result;
    int len =
        MultiByteToWideChar(CP_UTF8, 0, path.c_str(), static_cast<int>(path.length()), NULL, 0);
    if (len > 0) {
        result.resize(len);
        MultiByteToWideChar(CP_UTF8, 0, path.c_str(), static_cast<int>(path.length()), &result[0],
                            len);
    }
    return result;
}
#else
// For other platforms there is no need for any conversion, they
// support UTF-8 natively.
inline std::string DecodeUTF8Path(std::string path) { return path; }
#endif

#if defined(WIN32)
  #define stat _stat64i32
#endif

static int
statUTF8(const char* path, struct stat* info) {
#if defined(_WIN32)
    return _wstat(DecodeUTF8Path(path).c_str(), info);
#else
    return stat(path, info);
#endif
}

GTEST_API_ int main(int argc, char* argv[]) {
    testing::InitGoogleTest(&argc, argv);

    if (!::testing::FLAGS_gtest_list_tests) {
        if (argc != 3) {
            std::cerr << "Usage: " << argv[0] << " <test images path> <ktxdiff path>\n";
            return -1;
        }

#if defined(_WIN32)
        // Manually acquire the wide char command line in case a unicode
        // filename has been specified.
        int allargc;
        LPWSTR commandLine = GetCommandLineW();
        LPWSTR* wideArgv = CommandLineToArgvW(commandLine, &allargc);
        // commandLine still has all the arguments including those removed
        // by InitGoogleTest, hence the arg index calculation.
        imagePath = wideArgv[allargc - argc + 1];
        ktxdiffPath = wideArgv[allargc - argc + 2];
#else
        imagePath = argv[1];
        ktxdiffPath = argv[2];
#endif
        imagePath /= "";  // Ensure trailing / so path will be handled as a directory.

        struct stat info;

        if (statUTF8(imagePath.u8string().c_str(), &info) != 0) {
            std::cerr << "Cannot access " << imagePath << std::endl;
            return -2;
        }  else if (!(info.st_mode & S_IFDIR)) {
            std::cerr << imagePath << " is not a valid directory\n";
            return -3;
        }
    }

    return RUN_ALL_TESTS();
}
